Files
amethyst/quartz/tools/mdk-vector-gen/README.md
T
Claude 91f5d21cc3 test(marmot): MDK-authored Welcome interop vector + decryptor
Add a Rust generator under quartz/tools/mdk-vector-gen that emits a
Welcome, joiner KeyPackage, committer signer_pub, joiner's three
private keys, and a post-join MLS-Exporter KAT, using the same
openmls 0.8 backend MDK / whitenoise use. Commit its output at
quartz/src/commonTest/resources/mls/mdk-welcome.json.

MdkWelcomeInteropTest consumes the vector and proves Amethyst can:
  - parse a KeyPackage OpenMLS authored and verify its self-signature;
  - run the joiner's processWelcome end-to-end (HPKE unwrap of group
    secrets, welcome-key derivation, AEAD GroupInfo decrypt, ratchet
    tree decode, signer leaf lookup, GroupInfoTBS Ed25519 verify);
  - derive MLS-Exporter("marmot", "group-event", 32) byte-for-byte
    identically to openmls — the exact exporter Marmot uses for the
    outer ChaCha20 wrap on kind:445 events.

testBobDecryptsMdkApplicationMessagesFromAlice is @Ignored with a
detailed TODO because Amethyst's MlsGroup.encrypt/decrypt skip the
RFC 9420 §6.3.1 PrivateMessageContent framing (application_data<V> +
FramedContentAuthData + padding). Amethyst ↔ Amethyst works (both
sides omit it) but every cross-implementation PrivateMessage fails.
Tracked as a follow-up.

https://claude.ai/code/session_01HfHdd5S5rvxUW2ihEpLGJr
2026-04-21 05:04:41 +00:00

1.6 KiB

mdk-vector-gen

Rust helper that emits MLS interop test vectors from the same openmls 0.8 backend MDK/whitenoise uses. Produces quartz/src/commonTest/resources/mls/mdk-welcome.json, which [MdkWelcomeInteropTest] consumes to prove Amethyst can parse and decrypt a Welcome + application messages authored by the Rust side.

What the vector contains

  • joiner.init_priv / encryption_priv / signature_priv / signature_pub — all 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. This is the exporter Marmot uses to derive the outer ChaCha20 key for kind:445 events, so 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. (Amethyst decrypt currently skips PrivateMessageContent framing; the matching test is @Ignored until that is fixed.)

Regenerating

cd quartz/tools/mdk-vector-gen
cargo run --release > ../../src/commonTest/resources/mls/mdk-welcome.json

The generator uses fresh randomness each run, so the committed vector changes on regeneration — that is fine because the Kotlin test only asserts round-trip correctness against whatever is in the JSON. Commit the regenerated file if you change the generator.