nts library

Authenticated Network Time Security (RFC 8915) for Dart and Flutter.

Exposes a Rust-backed NTS-KE + AEAD-NTP client. Initialization has two independent layers; both must be in place before ntsQuery or ntsWarmCookies is called:

  1. Native platform bootstrap — captures the platform-specific handles (e.g. the Android JavaVM + application Context for rustls-platform-verifier) so TLS 1.3 can validate against the system trust store. On Android this happens automatically: the bundled NtsPlugin runs from GeneratedPluginRegistrant before Dart main(), so consumers do nothing. iOS/macOS/Linux/Windows have no JVM-style bootstrap step. Hosts that bypass the standard Flutter activity lifecycle (custom embeddings, isolates spawned ahead of plugin registration) can call com.nllewellyn.nts.PlatformInit.init(context) from Kotlin directly.
  2. Dart/FRB initializationawait NtsRustLib.init() once during startup before any nts* entry point. This loads the bundled Rust dylib through the Native Assets pipeline and wires the flutter_rust_bridge v2 dispatch table on the Dart isolate. Mandatory on every platform; the plugin layer cannot perform this step because it runs on the Android platform thread before the Dart isolate exists. Subsequent calls are no-ops.

The hand-written wrapper in src/api/nts.dart is the package's stable public contract: the underlying Rust-side bindings live in src/ffi/ and are an internal implementation detail. See ARCHITECTURE.md's "Public API stability layer" for the rationale.

Classes

MonotonicClock
A sleep-aware monotonic time source.
NtsClient
Owned NTS client handle.
NtsDnsPoolStats
Snapshot of the bounded DNS resolver pool counters.
NtsRustLib
Main entrypoint of the Rust API
NtsServerSpec
Address of an NTS-KE endpoint.
NtsSyncedTime
Synchronized clock produced by ntsGetTime / NtsClient.getTime.
NtsTimeSample
Successful authenticated NTPv4 sample.
NtsTrustStatus
Process-global trust-anchor diagnostic snapshot returned by ntsTrustStatus().
NtsWarmCookiesOutcome
Successful outcome of ntsWarmCookies.
PhaseTimings
Microsecond-resolution wall-clock breakdown of a successful ntsQuery or ntsWarmCookies call, surfaced on the phaseTimings field of NtsTimeSample / NtsWarmCookiesOutcome.

Enums

TimeoutPhase
Phase of an ntsQuery or ntsWarmCookies call whose wall-clock budget elapsed.
TrustBackend
Trust-anchor backend that authenticated a TLS chain, or that a process-global resolution attempt landed on.
TrustMode
Caller-selected policy for which trust-anchor backend an NtsClient is willing to run against. Set immutably at client construction and applied to every handshake the client initiates.

Constants

kDefaultBridgeConcurrencyCap → const int
Default per-call ceiling on concurrently dispatched bridge calls, applied isolate-wide by ntsQuery / ntsWarmCookies / NtsClient.query / NtsClient.warmCookies (the gate's state is Dart-side and isolate-local; each isolate gates its own calls over the shared process-wide flutter_rust_bridge worker pool).
kDefaultDnsConcurrencyCap → const int
Default per-call ceiling on in-flight DNS resolver workers, applied process-wide by ntsQuery / ntsWarmCookies / NtsClient.query / NtsClient.warmCookies.
kDefaultTimeout → const Duration
Default per-call wall-clock budget for ntsQuery / ntsWarmCookies / NtsClient.query / NtsClient.warmCookies.
kDefaultTimeoutMs → const int
Deprecated alias for kDefaultTimeout, expressed in milliseconds.

Functions

ntsDnsPoolStats() NtsDnsPoolStats
Snapshot the bounded DNS resolver pool counters. Synchronous (no future / isolate hop): backed by four atomic-relaxed loads, cheap enough to call from a UI poll loop.
ntsGetTime({required NtsServerSpec spec, TrustMode trustMode = TrustMode.platformWithFallback, List<int>? customRoots, DateTime? verificationTime, int? verificationTimeMs}) Future<NtsSyncedTime>
One-call "give me the correct time" convenience built on ntsWarmCookies + a burst of ntsQuery calls against the process-wide default client.
ntsQuery({required NtsServerSpec spec, Duration timeout = kDefaultTimeout, int? timeoutMs, int dnsConcurrencyCap = kDefaultDnsConcurrencyCap, int bridgeConcurrencyCap = kDefaultBridgeConcurrencyCap, DateTime? verificationTime, int? verificationTimeMs}) Future<NtsTimeSample>
Run a complete authenticated NTPv4 exchange against spec.
ntsTrustStatus() NtsTrustStatus
Snapshot the process-global trust-anchor diagnostic state. Synchronous (no future / isolate hop): backed by seven atomic loads, cheap enough to call from a UI poll loop or a pre-flight "can I even validate against the platform store?" check.
ntsWarmCookies({required NtsServerSpec spec, Duration timeout = kDefaultTimeout, int? timeoutMs, int dnsConcurrencyCap = kDefaultDnsConcurrencyCap, int bridgeConcurrencyCap = kDefaultBridgeConcurrencyCap, DateTime? verificationTime, int? verificationTimeMs}) Future<NtsWarmCookiesOutcome>
Force a fresh NTS-KE handshake against spec and return the cookie count along with the per-phase wall-clock breakdown of the handshake. Replaces any cached session for that spec.

Exceptions / Errors

NtsError
Failure surface for ntsQuery and ntsWarmCookies.
NtsErrorAuthentication
Variant: AEAD seal/open failed (tag mismatch or malformed input).
NtsErrorInternal
Variant: bug guard for unreachable internal states.
NtsErrorInvalidSpec
Variant: spec (or one of the integer arguments accompanying it) was rejected before any I/O happened. Surfaced from two layers:
NtsErrorKeProtocol
Variant: TLS handshake or NTS-KE record exchange failed.
NtsErrorNetwork
Variant: TCP/UDP I/O error or connection failure.
NtsErrorNoCookies
Variant: cookie jar empty after a handshake (server delivered none).
NtsErrorNtpProtocol
Variant: NTPv4 packet parsing or extension validation failed.
NtsErrorTimeout
Variant: wall-clock budget elapsed inside one of the call's pre-NTP or NTP phases.
NtsErrorTrustBackendUnavailable
Variant: caller selected TrustMode.platformOnly and the platform trust-anchor backend could not be constructed. New in 3.0.0; see NtsError.trustBackendUnavailable.