A Key Transparency implementation in Rust, conformant to
draft-ietf-keytrans-protocol-05
and draft-ietf-keytrans-architecture-09
(both vendored under docs/spec/).
Key Transparency is a verifiable, append-only log that maps user identifiers (labels) to values such as public keys. Every response the log gives is accompanied by a cryptographic proof, so a client can detect a server that serves a forged key, hides a version, rolls back its history, or shows different users different views — without trusting the server.
RuKT implements the participant roles:
| Role | Type | Entry point |
|---|---|---|
| Transparency Log (server) | gRPC service | KeyTransparencyImpl (src/service.rs) |
| Client | verifying library — search, update, monitoring, and offline credential verification | KtClient (src/client/core.rs) |
| Third-Party Auditor | log verifier | KtAuditor (src/client/auditor.rs) |
- Full client-side verification. The client independently reconstructs the log-tree root from every proof and checks the tree-head signature. Nothing a response claims is trusted: VRF outputs, commitments, prefix-tree roots, log roots, timestamps, and signatures are all verified. Tampering with any field is rejected.
- Linearizable, fork-evident. Clients persist their verified view and require
each new response to prove it extends the last one (
last, retained subtrees). A rolled-back or divergent-log head fails verification. Peers can exchange signed heads and recent distinguished-head root values over an out-of-band channel to detect a server that partitions its users; a double-signing server yields self-contained, third-party-verifiable evidence. - The full protocol-05 operation set:
- Search — greatest-version or a specific version of a label.
- Update — compare-and-swap on the label's greatest version, so concurrent writers can't fork a label's history; a behind client is transparently caught up on existing versions.
- Contact Monitor / Owner Initialization / Owner Monitoring — the split monitoring paths (§8.2/§8.3), replayed and verified client-side.
- Distinguished — walk the recent distinguished heads for fork detection.
- Credentials — standard and provisional credentials plus
CredentialUpdate, verified offline by a recipient without contacting the log.
- Deployment modes. Contact Monitoring and Third-Party Auditing are implemented; in auditing mode the client verifies the auditor's signed head (including a lagging auditor's sub-root) against its own reconstruction, and an auditor can bootstrap mid-history. Third-Party Management is out of scope.
- Deployment obligations from the architecture draft. A pluggable access policy gates Search and Update while monitoring stays unconditionally served; values and commitment openings are independently deletable for erasure, and expired non-greatest versions can be pruned by maximum lifetime.
- Cipher suites.
KT_128_SHA256_ED25519(Ed25519 + ECVRF-EDWARDS25519-SHA512-TAI) andKT_128_SHA256_P256. - Storage. Persistent RocksDB with tuned write throughput (large memtables,
parallel background jobs, batched writes) and a
DashMapin-memory cache over hot prefix-tree nodes. - Throughput. The update batcher pipelines work into four phases —
sequential versioning, parallel VRF/commitment crypto (
spawn_blocking), sequential Merkle append, and parallel proof generation — dropping the write lock early so proofs assemble concurrently.src/bulk.rsbuilds large trees offline viarayonand SST ingestion.
- Rust & Cargo (edition 2024):
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - Protobuf compiler (
protoc):brew install protobuf/sudo apt install protobuf-compiler - Clang/LLVM (for RocksDB):
xcode-select --install/sudo apt install build-essential clang libclang-dev
cargo build --releasecargo runThe server listens on 0.0.0.0:8081 in Contact Monitoring mode. On first start
it generates a signing key and a VRF key into ./kt_data and reuses them on
every later start, serving the log from the same directory:
=== SERVER PUBLIC CONFIG (distribute to clients out of band) ===
SIG_KEY: <hex>
VRF_KEY: <hex>
cipher_suite: 0x0002 mode: contact-monitoring
config: ./kt_data/config.json
================================================================
Key Transparency Server listening on 0.0.0.0:8081
--help lists the full set of flags (each also settable by environment
variable): listen address, data directory, cipher suite, auditor keys, and the
log's mode parameters. --dump-config writes the public config and exits.
The protocol's security considerations (§16) call for transport-layer
encryption: set KT_TLS_CERT and KT_TLS_KEY to PEM paths to serve gRPC over
TLS in-process, or leave them unset (plaintext) and terminate TLS in a fronting
proxy. Clients can dial https:// endpoints directly.
A verifying client must be told the log's configuration — cipher suite, keys,
and mode parameters — out of band; that is the trust root the whole protocol
rests on, and fetching it from the log being verified proves nothing. The server
writes it to <data-dir>/config.json on every start, which is the artifact to
pre-distribute over a trustworthy channel. The mode parameters are signed into
every tree head, so a client that guesses them instead of reading the published
config will reject the log's signature.
With the server running, its published config is all the client needs:
cargo run --example client_demoIt reads kt_data/config.json and dials http://127.0.0.1:8081; override with
KT_CONFIG and KT_URI.
Connected to Key Transparency Server
Registering user 'bob'...
Update successful. New Tree Size: 1
Searching for user 'bob'...
Verified Value: "bob_pk_v1"
A public deployment runs at https://kt.guyfischman.com (contact-monitoring
mode, Ed25519), seeded with alice, bob, and carol. To exercise every
client action against it and verify each response:
git clone https://github.com/guyfischman/RuKT.git
cd RuKT
# fetch the log's published config — the trust root the client verifies against
curl -s https://kt.guyfischman.com/config.json > live-config.json
KT_CONFIG=live-config.json KT_URI=https://kt.guyfischman.com \
cargo run --release --example endpoint_tourconnected to https://kt.guyfischman.com
update registered tour-<nonce> at v0 and v1
search greatest vSome(1) = "pk_v1"
search version 0 "pk_v0"
search alice vSome(0) = "alice_pk_v1"
contact_monitor OK
distinguished N head(s)
owner_init/monitor OK
get_credential v1 verified offline (provisional)
credential_update verified offline
persist/reload new head proven consistent with the retained view
gossip head peers agree on the same signed head
gossip roots distinguished roots agree across peers
fork detection forged divergent head rejected
Every endpoint verified against https://kt.guyfischman.com.
Each line is a distinct client action — registration and update (the Update
RPC; the first creates version 0), search (greatest and specific version),
contact monitoring, the distinguished walk, owner initialization and monitoring,
and offline credentials with a credential update. The last four lines go beyond
the RPC surface: durable fork-evident state proven consistent across a restart,
two independent clients cross-checking the operator's signed heads and
distinguished roots over the out-of-band gossip channel (§10), and a forged
divergent head being rejected. The client independently reconstructs and checks
every proof and signature, so a line prints only if verification passed. The
tour registers a unique tour-<nonce> label and writes a few throwaway entries,
so it is safe to run repeatedly and by many people at once.
The last line depends on the log's state: a credential update (§14.2) only
applies to a provisional credential and needs a distinguished entry past its
terminal, so the tour prints credential_update n/a (...) when the credential
is already anchored or no such entry has formed yet.
The config served at /config.json comes from the log itself, so on its own it
proves nothing: cross-check its two public keys against the copy published out
of band before trusting a run.
KtClient performs the RPC and the verification together; a call returns only
if the response's proof checks out.
use rukt::client::KtClient;
let mut client = KtClient::connect(uri, public_config).await?;
client.persist_to("client-state.json")?; // durable, fork-evident state
client.update(b"alice".to_vec(), b"alice_pk".to_vec()).await?;
let resp = client.search(b"alice".to_vec(), None).await?; // verified greatest-version search
let value = resp.value.unwrap().value;
client.contact_monitor(b"alice".to_vec()).await?; // discharge monitoring obligations
// offline credentials: the issuer hands `cred` to a recipient out of band
let cred = client.get_credential(b"alice".to_vec()).await?;
recipient.distinguished(None).await?; // learn recent distinguished heads
recipient.verify_credential(&cred)?; // verified without contacting the logcargo test --workspace --lib --bins --tests --examplesThe suite covers each operation client-verified in both deployment modes, per-field adversarial rejection (a tampered value, opening, ladder, commitment, timestamp, root, or signature is refused), cross-operation state continuity across restarts, and fork/rollback rejection.
Formatting and lints are enforced as blocking CI gates:
cargo fmt --all --check
cargo clippy --workspace --all-targets -- -D warningsDeterministic known-answer vectors for the hashed and signed byte formats
(commitment, VRF, tree hashing, tree-head TBS) are pinned in
src/integration/interop_vectors.rs and
mirrored, with documented inputs and spec-section anchors, in
docs/spec/interop-vectors.json for
cross-implementation comparison.
cargo benchCriterion benchmarks (benches/kt_benchmarks.rs)
populate large trees via src/bulk.rs and measure search, monitor, and update
at scale, using RocksDB checkpointing so each iteration starts from identical
state.
proto/ gRPC + wire message definitions (protocol-05)
src/service.rs gRPC server, deployment-mode wiring, access policy
src/tree/ log tree, prefix tree, traversals, credentials, pruning
src/client/ KtClient (verifying), KtAuditor, offline verifier, gossip
src/crypto/ commitments, VRF, signatures, TLS presentation encoding
src/bulk.rs offline bulk tree population
docs/spec/ vendored IETF drafts and interop vectors