Files
amethyst/quartz/tools/mdk-vector-gen/README.md
Claude 57d4d34bd5 refactor(quartz): consume the MLS engine from mls-kmp instead of quartz/mls
The binding-agnostic RFC 9420 engine moved to vitorpamplona/mls-kmp
(com.vitorpamplona.mls:mls), published like negentropy-kmp. Quartz now
exposes it as an api dependency, so Marmot, cordn, commons, cli and
marmotBench only change imports: com.vitorpamplona.quartz.mls ->
com.vitorpamplona.mls.

- settings.gradle.kts: -PmlsKmpDir=../mls-kmp builds a local checkout in
  place of the artifact, the same way quicKmpDir works.
- quartz/mls/crypto/X25519 stays as a deprecated binary-compat shim: the
  published quic-kmp 1.0.0 calls it in its TLS handshake, and without it
  nests and marmotQuic fail with NoClassDefFoundError at runtime.
- The two engine tests that decode Marmot components as fixtures moved to
  marmot/appComponents; marmot-current-profile.json moved to
  resources/marmot/. Tests reaching engine internals opt in to
  @InternalMlsApi.
- BindingIsolationTest drops the engine half: mls-kmp has no Quartz
  dependency, so the module graph enforces it.
- tsmls-vector-gen moved to mls-kmp; mdk-vector-gen stays (shared MDK pin
  with marmot-profile-gen) and now writes mdk-welcome.json into mls-kmp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TBPmwJ7qKk977tdjUCNpJZ
2026-10-20 19:18:44 +00:00

91 lines
4.2 KiB
Markdown

# mdk-vector-gen
Rust helpers that emit MLS and Marmot interop test vectors from the same
OpenMLS backend MDK builds against.
**The dependency pin is the point of this tool.** `Cargo.toml` tracks
`erskingardner/openmls` at the exact rev MDK's root `Cargo.toml` names, built
with the `extensions-draft` feature. Stock crates.io `openmls` does not carry
`app_data_dictionary` / `app_components` / `app_data_update`, and the current
Marmot profile is defined entirely in those terms — so vectors generated from
the published crate cannot exercise it. When MDK bumps its OpenMLS rev, bump it
here in the same change.
## Binaries
### `marmot-profile-gen` → `marmot-current-profile.json`
The current-profile Marmot vector: a group built the way MDK's `cgka-engine`
builds one.
- `RequiredCapabilities` = extension `0x0006` (`app_data_dictionary`) +
proposal `0x0008` (`app_data_update`).
- GroupContext `app_data_dictionary` carrying the required-component list
(`0x0001`) plus `marmot.group.profile.v1` (`0x8001`),
`marmot.group.admin-policy.v1` (`0x8003`),
`marmot.transport.nostr.routing.v1` (`0x8004`) and
`marmot.group.lifecycle.v1` (`0x800c`). Component `0x8009` is *required* but
leaf-only, so it deliberately has no GroupContext data.
- Every member LeafNode dictionary carrying the supported-component list, an
empty `safe_aad` list, and the 104-byte
`marmot.member.account-identity-proof.v2` component.
- The KeyPackage-level dictionary carrying the empty-data
`last_resort_key_package` component (`0x0004`) — last resort is not an MLS
extension type in this profile.
- Handshake messages as `PublicMessage`, matching Marmot's pinned wire format;
the Add commit is emitted so the peeler has a real one to authenticate.
- Exporter KATs for both `MLS-Exporter("marmot", "group-event", 32)` and the
conformance commitment from `foundation/conformance.md`.
The identity-proof encoder is hand-rolled from the spec text rather than pulled
from MDK, and `assert_spec_proof_vector()` checks it against the fixed vector
published in `app-components/account-identity-proof-v2.md` before anything else
runs. If the generator starts up at all, the canonical kind-450 event
serialization, its id, the BIP-340 signature and the 104-byte component layout
all match the spec byte-for-byte.
```
cd quartz/tools/mdk-vector-gen
cargo run --release --bin marmot-profile-gen \
> ../../src/commonTest/resources/marmot/marmot-current-profile.json
```
### `mdk-vector-gen` → `mdk-welcome.json`
The MLS-core vector, unchanged in intent: it proves the MLS engine can parse and
decrypt a Welcome plus application messages authored by the Rust side. It
builds a plain OpenMLS group with no Marmot profile state, which is exactly
what makes it a clean test of the key schedule alone.
- `joiner.init_priv` / `encryption_priv` / `signature_priv` / `signature_pub` —
the private key material the joiner needs to drive `MlsGroup.processWelcome`.
- `joiner.key_package` (MlsMessage-wrapped) and `key_package_raw`.
- `welcome` (MlsMessage-wrapped) — Alice's Welcome for Bob.
- `committer.signer_pub` — Alice's Ed25519 signature public key.
- `exporter.{label,context,length,secret}` — `MLS-Exporter("marmot",
"group-event", 32)` derived from Bob's post-join state. A match here means
Amethyst's whole post-join key schedule agrees with OpenMLS byte-for-byte.
- `app_messages_alice_to_bob[]` — Alice-sent PrivateMessage bytes plus the
expected plaintext.
```
cargo run --release --bin mdk-vector-gen \
> ../../../../mls-kmp/mls/src/commonTest/resources/mls/mdk-welcome.json
```
The engine moved to vitorpamplona/mls-kmp, and so did this vector and the
`MdkWelcomeInteropTest` that reads it; the path above assumes `mls-kmp` is
checked out beside this repo. The generator stays here, in one crate with
`marmot-profile-gen`, because both must track the same MDK OpenMLS pin.
### `emit-joiner-kp`, `verify-amethyst`
Debug helpers for driving one side of a join by hand.
## Regenerating
Both generators use fresh randomness each run, so the committed vectors change
on regeneration — that is fine, because the Kotlin tests assert round-trip
correctness against whatever is in the JSON rather than against fixed bytes.
Commit the regenerated file if you change a generator.