purecrypto 0.6.29

A pure-Rust cryptography toolkit with no foreign-code dependencies, from constant-time primitives up to keys, X.509 and TLS.
Documentation
<script setup>
import { ref, reactive, watch, onBeforeUnmount } from 'vue';
import { state, pc } from '../store.js';
import DemoCard from './ui/DemoCard.vue';

const recipient = ref(null); // keeps the private (decapsulation) key
const ekDer = ref(null); // public encapsulation key, DER
const r = reactive({ ct: null, ssA: null, ssB: null, agree: false, bytes: 0 });

function newRecipient() {
  if (state.status !== 'ready') return;
  if (recipient.value) recipient.value.free();
  recipient.value = pc.mlkem(pc.MLKEM.K768);
  ekDer.value = recipient.value.publicDer();
  encapsulate();
}
function encapsulate() {
  if (!recipient.value) return;
  const { ct, ss } = pc.mlkemEncaps(pc.MLKEM.K768, ekDer.value); // sender side
  const ssB = recipient.value.decaps(ct); // recipient side
  r.ct = ct;
  r.ssA = ss;
  r.ssB = ssB;
  r.bytes = ct.length;
  r.agree = ss.length === ssB.length && ss.every((x, i) => x === ssB[i]);
}

const short = (bytes, head = 24) =>
  bytes ? pc.toHex(bytes.slice(0, head)) + ` … (${bytes.length} bytes)` : '—';

watch(() => state.status, (s) => s === 'ready' && newRecipient(), { immediate: true });
onBeforeUnmount(() => recipient.value && recipient.value.free());
</script>

<template>
  <DemoCard
    index="D.03"
    title="ML-KEM key encapsulation"
    subtitle="The NIST FIPS 203 post-quantum KEM (ML-KEM-768). A sender encapsulates to the recipient's public key; both sides derive the same 256-bit secret — without it ever crossing the wire."
    api="purecrypto::mlkem — encapsulate / decapsulate"
  >
    <template #badge><span class="tag pq">post-quantum</span></template>

    <div class="flow">
      <div class="party">
        <span class="tag public">recipient public key</span>
        <code class="hex public">{{ short(ekDer, 24) }}</code>
      </div>
      <div class="party">
        <span class="tag public">ciphertext (sender → recipient)</span>
        <code class="hex public">{{ short(r.ct, 24) }}</code>
      </div>
    </div>

    <div class="secrets">
      <div class="secret-box">
        <span class="mono who">sender derives</span>
        <code class="hex secret">{{ r.ssA ? pc.toHex(r.ssA) : '—' }}</code>
      </div>
      <div class="secret-box">
        <span class="mono who">recipient derives</span>
        <code class="hex secret">{{ r.ssB ? pc.toHex(r.ssB) : '—' }}</code>
      </div>
    </div>

    <div class="verdict" :class="{ ok: r.agree }">
      <span v-if="r.agree">✓ shared secrets match — {{ r.ssA.length * 8 }}-bit key agreed</span>
      <span v-else>—</span>
    </div>

    <div class="actions">
      <button class="btn small" @click="encapsulate">↻ encapsulate again</button>
      <button class="btn small ghost" @click="newRecipient">new recipient key</button>
    </div>
  </DemoCard>
</template>

<style scoped>
.flow,
.secrets {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}
.party,
.secret-box {
  background: var(--ink-2);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm);
  padding: 13px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 9px;
}
.who {
  font-size: 0.72rem;
  color: var(--muted);
  letter-spacing: 0.06em;
}
.verdict {
  font-family: var(--font-mono);
  font-size: 0.84rem;
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line-soft);
  color: var(--muted);
  text-align: center;
}
.verdict.ok {
  color: var(--teal-2);
  background: var(--teal-glow);
  border-color: rgba(47, 224, 200, 0.3);
}
.actions {
  display: flex;
  gap: 10px;
}
.btn.small {
  padding: 8px 13px;
  font-size: 0.76rem;
}
.btn.ghost {
  background: transparent;
}
@media (max-width: 560px) {
  .flow,
  .secrets {
    grid-template-columns: 1fr;
  }
}
</style>