INVITE ONLY
OBSERVATORY

Research

protocol-spec

Identity and Key Transparency: Serpent ID, Profile ID, and the KTS Log

Version
v1.0
Category
protocol-spec
Published
Last updated
Authors
QERYX Research
Primitives BLAKE3 ML-DSA-87 SHA-512 HMAC-SHA-256
What changed v1.0
  1. v1.0 First publication of the identity layer and KTS chapters.
Contents
  1. §1 Two identity layers
  2. §2 Blinding the social graph
  3. §3 The Key Transparency System
  4. §4 Distrust of the operator
  5. §5 Wire-stable byte recipes
  6. §6 References

QERYX identity is two layers: a 256-bit Serpent ID that never leaves the device, and a public Profile ID derived from it one-way through BLAKE3 keyed hashing. Public keys bind to Profile IDs through the Key Transparency System — an append-only Merkle log with ML-DSA-87-signed tree heads — so a peer's real key is auditor-verifiable without trusting the operator.

Two identity layers

The Serpent ID is 256 bits of seed entropy: an OS secure-random draw XORed with 256 bits from the certified-QRNG pool, so an attacker controlling one entropy source cannot bias the result. It is stored device-only under biometric protection and appears on no wire, ever.

Three subkeys derive from it through BLAKE3 keyed-derive, each under its own domain string — a break of one derivation stream does not touch another:

DomainSubkeyUse
qeryx.identity.profile.v1Profile IDpublic messaging handle
qeryx.identity.vault.v1vault keyQVault encryption
qeryx.identity.signing.v1signing seedderives the ML-DSA-87 identity keypair

The derivation is one-way: the Profile ID leaks nothing about the Serpent ID, and the parser hard-rejects any identifier that does not carry the exact PROFILE- prefix discipline — test vectors in the source lock this at the byte level.

Blinding the social graph

The relay locates conversations without ever seeing a plaintext Profile ID. Each conversation carries a random 32-byte salt, and membership is stored as SHA-256(salt || profile_id) — the same Profile ID hashes to a different value in every conversation, so an operator with database access cannot correlate hashes across conversations to enumerate who talks to whom.

Presence uses a second, disjoint namespace: a truncated HMAC-SHA-256 token over "pair:" || presenter || "|" || peer, asymmetric by construction so a recipient cannot replay an inbound token to spoof the presenter. Wire-stable test vectors pin both recipes; if either vector ever fails, clients and database desync — the tests are tripwires.

The Key Transparency System

KTS is an append-only Merkle log of (profile_id, ml_dsa_87_pubkey) bindings, modeled on RFC 6962 and RFC 9162 and adapted to ML-DSA-87 so the operator's commitment survives a quantum adversary.

Leaf:  H = SHA-512[..32]( 0x00 || canonical(entry) )
Node:  H = SHA-512[..32]( 0x01 || left || right )

The domain-separator byte between leaf and node hashing forecloses second-preimage confusion between the two levels (RFC 6962 §2.1). Every Signed Tree Head is signed under the FIPS 204 context QERYX-KTS-STH-v1; inclusion proofs authenticate a single binding, and consistency proofs force every new tree head to be an append-only extension of every prior one — truncation or rewrite produces a proof failure, treated as operator equivocation, a hard error.

An earlier design signed the domain string inside the message body; an external review finding (F-002, closed 2026-04-30) moved domain separation into the FIPS 204 context framing, eliminating a cross-protocol replay class.

Distrust of the operator

The operator is assumed hostile. Three attacks, three answers:

Operator attackDefense
Equivocate — show different logs to different clients two conflicting signed tree heads are dispositive evidence; clients gossip STHs out-of-band (mesh, paired devices, QR) so a split view surfaces even offline
Insert silently — swap a key without the owner noticing clients pin the operator key on first contact and hard-fail any tree head that does not verify against the pin
Erase history — remove a compromising binding consistency proofs bind every new head to every old one; erasure cannot produce a valid proof

Operator key rotation is deliberately manual: a legitimate rotation is co-signed by the currently-pinned key, and accepting a new key after a compromise requires an explicit, biometric-gated "Reset Operator Trust" action with confirmation from at least two independent channels. An adversary cannot induce silent re-pinning.

Wire-stable byte recipes

A reviewer can re-derive any KTS proof from these alone:

canonical(entry) = u64_be(index) || u64_be(timestamp_secs)
                || u32_be(len(profile_id)) || profile_id
                || u32_be(len(public_key)) || public_key

sth_payload = u64_be(tree_size) || root_hash || u64_be(ts) || log_id   // 80 B
sth_sig     = ML-DSA-87.SignWithCtx(sk_op, ctx = "QERYX-KTS-STH-v1", msg = sth_payload)

Big-endian length prefixes make the parse unambiguous; the entry's own internal prefixes are the envelope.

References

  1. QERYX Protocol Specification §3–§4 — docs/SPEC/02-identity-and-kts.md; implementation crypto-core/src/identity.rs, crypto-core/src/merkle_kts.rs.
  2. RFC 6962 — Certificate Transparency; RFC 9162 — Certificate Transparency v2.
  3. NIST FIPS 204 — ML-DSA, §5.2 context framing.
  4. Verify Your Safety Number — the user-facing check this log underwrites.