# metamorphic-log
A tamper-evident, append-only **transparency log** engine and verification SDK.
Built by [Moss Piglet Corporation](https://mosspiglet.dev) to support privacy-
first software, including [Metamorphic](https://metamorphic.app) and [Mosslet](https://mosslet.com).
It implements the cryptographic _verification core_ over an append-only Merkle
log (RFC 6962 / RFC 9162), wraps the [C2SP `tlog-tiles`][tlog-tiles] substrate
for storage and serving, supports externally **witnessed** checkpoints for
anti-equivocation, layers in **hybrid post-quantum** checkpoint signatures, and
adds CONIKS-style index privacy via a swappable VRF.
> **Status:** v0.1, building slice-by-slice. Implemented: the **conformance
> core** (canonical Layer-0 leaf encoding, RFC 6962 Merkle hashing, RFC 6962 /
> RFC 9162 inclusion + consistency verification), the **C2SP `tlog-tiles`**
> substrate with `checkpoint` / `signed-note` parsing and classical Ed25519
> witness verification, **additive hybrid post-quantum** checkpoint signing,
> **CONIKS** index privacy (swappable VRF + SHA3-512 commitments + lookup/absence
> proofs), the signed per-namespace **policy** layer with declared == observed
> enforcement, and the **browser verification + monitor SDK** (WASM). The leaf
> layer is application-agnostic — any app defines its own opaque record type
> under a versioned `<namespace>/<record-type>/v<N>` context label; the bundled
> `key_history_v1` conformance instance (the format used by
> [Mosslet](https://mosslet.com), the first consumer) is reproduced
> byte-for-byte by the native crate **and** the WASM SDK. Ingestion/scale
> primitives land in a later slice.
## Browser verification + monitor SDK (WASM)
The crate ships a `wasm-bindgen` personality (the `wasm` module, `wasm32`-only)
so a browser can **monitor the log itself** instead of trusting a server. It is
a thin shell over the rlib core — no parallel logic — published to npm as
[`@f0rest8/metamorphic-log`](npm-README.md). It exposes the full verification
surface: `verifyInclusion` / `verifyConsistency`, checkpoint/signed-note
verification (Ed25519 + hybrid PQ), CONIKS `coniksVerifyLookup` /
`coniksVerifyAbsence`, and `signedPolicyVerify` + the declared == observed
`policyEnforce*` checks. A **cross-language byte-parity KAT**
(`tests/cross_language.rs`, run under `wasm-pack test --node`) proves the WASM
exports reproduce the native KAT vectors byte-for-byte. The Elixir NIF
(`metamorphic_log`, Rustler + dirty schedulers) is a deferred follow-up in its
own sibling Hex package, mirroring the `metamorphic_crypto` precedent of a thin
NIF over the published crate.
## Verifying proofs
```rust
use metamorphic_log::{verify_inclusion, verify_consistency};
// Prove a leaf is committed at `index` in a tree of `size` whose head is `root`.
verify_inclusion(index, size, leaf_hash, &audit_path, root)?;
// Prove the tree of `size2`/`root2` is an append-only extension of `size1`/`root1`.
verify_consistency(size1, size2, &proof, root1, root2)?;
```
## Defining a leaf (any application)
A log leaf is **opaque, app-defined bytes** — the Merkle layer never inspects
them, so your canonical record drops in with zero reformatting. You choose a
versioned context label (`"acme/user-keys/v1"`, `"example-app/audit-event/v2"`,
…) as the domain separator for the per-record content hash. The bundled
`metamorphic_log::leaf::key_history_v1` module is a worked example of such a
record type (and the byte-locked conformance fixture); model your own on it.
## Index privacy (CONIKS)
A `coniks::ConiksDirectory` maps identities to committed values at
VRF-derived, privacy-preserving tree positions, and answers lookups with
**presence** or **absence** proofs that a relying party verifies independently —
from only the namespace, the VRF public key, the directory root, and the proof.
```rust
use metamorphic_log::coniks::{ConiksDirectory, LookupResult, Namespace, verify_lookup};
use metamorphic_log::vrf::Ecvrf;
let mut dir = ConiksDirectory::new(Namespace::parse("acme")?, Box::new(Ecvrf));
dir.insert(b"alice@example.com", b"key-history-head")?;
let root = dir.root();
let LookupResult::Present(proof) = dir.lookup(b"alice@example.com")? else {
unreachable!()
};
// Independent verification — no access to the directory needed.
let value = verify_lookup(
&Ecvrf, dir.namespace(), dir.vrf_public_key(), &root, b"alice@example.com", &proof,
)?;
assert_eq!(value, b"key-history-head");
```
The VRF is swappable behind the `vrf::Vrf` trait (classical ECVRF today; a
hybrid PQ construction slots in later with no format change). Commitments
(`commitment`) are SHA3-512 — post-quantum and binding regardless of the VRF.
## Single source of truth for primitives
This crate contains **no cryptographic primitives of its own**. Every hash
(SHA-256 / SHA3-512), signature (composite hybrid PQ), KEM (ML-KEM), and KDF
comes from [`metamorphic-crypto`][crypto], the audited, RustCrypto-only core.
**There is no parallel crypto stack here.**
## What a transparency log does — and does not — provide
A transparency log gives you, **after** you have a key to anchor on:
- **Continuity** — the history you observe is append-only and self-consistent
over time (consistency proofs).
- **Anti-equivocation** — the operator cannot show different histories to
different observers without detection, because independent **witnesses**
co-sign checkpoints.
- **Tamper-evidence** — any retroactive edit to the log breaks an inclusion or
consistency proof.
It does **not** solve:
- **First-contact / bootstrap trust.** A transparency log cannot tell you whether
the _first_ key you ever saw for a peer is the genuine one. That is a
Trust-On-First-Use (TOFU) problem your application must handle separately from
this library (for example, with out-of-band fingerprint or safety-number
verification). The log makes a key _accountable over time_; it does not vouch
for its origin.
## Cryptographic posture
- **PQ from day one** for integrity, authentication, confidentiality, and
commitments. Checkpoints are designed for hybrid post-quantum signing via
`metamorphic-crypto`'s composite (ML-DSA + classical) signatures.
- **Index-privacy** (the CONIKS VRF) defaults to a classical
ECVRF-edwards25519-SHA512-TAI construction (RFC 9381 ciphersuite `0x03`),
behind a swappable trait. The constant-time `ELL2` (`0x04`) suite and a
hybrid (PQ + classical) output path are designed in but not built — the
former lands with a curve backend that exposes a conformant hash-to-curve, the
latter when an audited lattice VRF exists. This is the _only_ layer with a
classical default in v0.1.
- Primitives are hybrid post-quantum, pure-Rust, and NCC-audited (via
`metamorphic-crypto`). They are **not** FIPS-validated, and this project does
not claim FIPS validation.
## Standards spine
- **RFC 6962** / **RFC 9162** — Merkle log; inclusion + consistency proofs
- **C2SP** [`tlog-tiles`][tlog-tiles], `tlog-witness`, `checkpoint` /
`signed-note` — interoperable substrate enabling reciprocal witnessing
- **RFC 9381** — ECVRF-edwards25519 (CONIKS index privacy)
- **FIPS 203 / 204** + **CNSA 2.0** — post-quantum primitives (via
[`metamorphic-crypto`][crypto])
- **NIST SP 800-56C / 800-108** — KDF roles
## Module layout
| `leaf` | 0 | Canonical, length-prefixed leaf encoding |
| `merkle` | 1 | RFC 6962 SHA-256 tree-node hashing (fixed, witness-audited) |
| `proof` | 1 | Inclusion + consistency proof verification |
| `checkpoint` | 2 | Signed-note / witnessed checkpoints; hybrid PQ signing |
| `vrf` | 3 | Swappable VRF trait; classical ECVRF (RFC 9381 TAI) default |
| `commitment` | 3 | SHA3-512 hiding/binding index→value commitments |
| `coniks` | 3 | Per-namespace directory; presence + absence (index privacy) |
| `policy` | 0 | Signed, versioned namespace policy; declared == observed |
| `note` | 2 | C2SP `signed-note` parse/verify (Ed25519 + hybrid PQ lines) |
| `tile` | 2 | C2SP `tlog-tiles` coordinates / serving geometry |
| `wasm` | — | Browser verification + monitor SDK (`wasm32`-only) |
| `error` | — | Crate-wide error type |
## Safety & supply chain
- `#![forbid(unsafe_code)]` at this layer
- RustCrypto-only dependencies; primitives delegated to `metamorphic-crypto`
- Edition 2024, MSRV 1.85, dual-licensed `MIT OR Apache-2.0`
- CI runs `fmt --check`, `clippy -D warnings`, tests, a `wasm32-unknown-unknown`
check, a `wasm-pack` SDK build, the cross-language byte-parity KAT
(`wasm-pack test --node`), `cargo audit`, and an MSRV-floor build; all action
refs are SHA-pinned. The tagged release pipeline adds CycloneDX SBOM, cosign
keyless signing, build-provenance attestation, and OIDC trusted publishing to
crates.io + npm
- See [`SECURITY.md`](SECURITY.md) for the disclosure process
## License
Licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE))
- MIT license ([LICENSE-MIT](LICENSE-MIT))
at your option.
[crypto]: https://github.com/moss-piglet/metamorphic-crypto
[tlog-tiles]: https://github.com/C2SP/C2SP/blob/main/tlog-tiles.md