Post-quantum (ML-DSA-65 / ML-KEM-768) client-side cryptography for SAIHM non-custodial agent memory — your keys never leave the client: per-cell AES-256-GCM, provable crypto-shred erasure (GDPR Art. 17), authenticated sharing with one-step revoke. One cell, many shapes (polymorphic). One memory layer for fleet-scale swarms of many agents — homogeneous or disparate — portable across every LLM client and surviving any single vendor's product changes. Pairs with @saihm/mcp-server-pro (the production sealing thin-client).
This is a cryptographic library, not an MCP server. It exports 16 pure functions with strong descriptions (100-300 chars each) and detailed parameter documentation. All tools have explicit input schemas with typed fields and descriptions. However, it lacks output schema documentation, error handling guidance, and composition patterns typical of agent tools. The naming is domain-specific (deriveIdentity, sealCell, unwrapSharedDek) rather than verb_noun agent patterns. No tool descriptions explain WHEN to call them or what downstream tools accept their outputs. Security-focused descriptions are excellent but not optimized for LLM tool selection.
Accept iff `incoming` strictly exceeds the current high-water mark (or it is the first write). Canonical server-issued monotonic sequence rule. The high-water mark is ENFORCED by the SAIHM server-side runtime; this module is the single source of truth for the rule so that the server and the client test-suite agree. Because `seq` is inside the ML-DSA-signed record, a client cannot advance it without the signing key, and SAIHM rejects any write whose `seq` is not strictly greater than the current high-water mark — preventing rollback to stale content and re-instatement of a forgotten cell.
Decode a JSON-safe WireEnvelope to a BlindEnvelope. TOTAL over UNTRUSTED input: every field is validated to its JSON wire type and canonical form, and the whole body is wrapped so STRUCTURAL malformation (e.g. a null/absent publicMeta or a non-object body) surfaces as the SAME typed WireFormatError — never an untyped TypeError. The blind server treats WireFormatError as a 4xx reject; with the total-predicate verifyEnvelope this leaves no unhandled-exception / DoS surface on the decode->verify path.
Decode a JSON-safe WireIdentityRecord to an IdentityRecord. TOTAL over UNTRUSTED directory input — like decodeEnvelope, every field is canonical lowercase hex of its exact fixed length, and any malformation surfaces as a single typed WireFormatError.
Derive a full client identity from a master secret (>= 32 bytes of high-entropy material). Deterministic: same secret (+ tag) => same identity, on any device. Seed derivation (seed-then-keygen; the HKDF domains yield SEEDS, @noble does keygen): kgRoot = HKDF(ikm=masterSecret, info=MPS-PQC-KEY-GEN-v1), mldsaSeed = HKDF(ikm=kgRoot, info=MPS-AGENT-IDENTITY-v1, 32B), mlkemSeed = HKDF(ikm=kgRoot, info=MPS-MLKEM-ENCAP-v1, 64B), kek = HKDF(ikm=masterSecret, info=MPS-KEK-v1, 32B)
No output schemas documented. Tools return complex types (BlindEnvelope, IdentityRecord, ShareEnvelope) but descriptions do not specify return field structure, types, or which fields downstream tools require.
Tool names do not follow verb_noun pattern. Names like 'unwrapDek', 'openCell', 'acceptSeq' are domain-specific and do not start with action verbs (get_, create_, verify_, etc.). This reduces LLM discoverability when many tools are available.
No error handling guidance. Descriptions do not explain what errors can occur, when to retry, or how to recover. E.g., 'Throws on wrong KEK' (unwrapDek) does not tell an agent what to do next.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | F | 44 | 2026-07-28+ | v2 |
Encode a BlindEnvelope to JSON-safe wire format (hex for bytes, decimal string for bigint).
Encode an IdentityRecord to JSON-safe wire format (hex for bytes).
Owner-side decrypt: unwrap DEK with the client KEK, then open. Throws if KEK wrong or shredded.
Open with a known DEK (recipient path after share-unwrap; or owner after unwrapDek).
Seal a cell with per-cell envelope crypto. A fresh RANDOM DEK encrypts each cell; the DEK's only persisted form is `wrappedDek` (AEAD-wrapped under the client KEK). SAIHM stores the envelope blind: it can verify the signature but holds no key to read `ciphertext` or unwrap `wrappedDek`. GDPR Art.17 crypto-shred = destroy the SAIHM-side `wrappedDek` (the DEK was random and is derivable from nothing else).
Blind, authenticated cross-agent sharing. The sharer KEM-encapsulates to the recipient's VERIFIED ML-KEM public key and AEAD-wraps the cell DEK under the resulting shared secret (KEM-then-AEAD). SAIHM blind-stores the share envelope; it never sees a DEK and cannot substitute keys undetected (verifyIdentityRecord pins the recipient out-of-band).
Sign an opaque challenge (e.g. a SAIHM onboard nonce) with this identity's ML-DSA-65 secret key, returning the detached signature bytes. Self-onboarding clients use this to prove control of the agent identity in the endpoint's challenge/response, minting their own short-lived access token without a human pasting one. The secret key never leaves the client.
Unwrap the per-cell DEK using the client-held KEK. CLIENT-SIDE ONLY. Throws on wrong KEK.
Recipient path — authenticate THEN unwrap, failing closed. The sharer's signature is MANDATORY (the "authenticated sharing" guarantee): a third party who can read the recipient's public ML-KEM key from the untrusted directory could otherwise craft a share that decapsulates for the recipient while falsely claiming another agent as sharer. Verification order: (1) the share is addressed to this agent; (2) the claimed `sharerAgentIdHash` matches the out-of-band-pinned sharer public key; (3) the sharer signature over the whole share verifies; only then (4) KEM-decapsulate + GCM-unwrap.
Blind verification — exactly what SAIHM runs before persist/anchor. True iff: (1) the identity binding holds: sha256(mldsaPubKey) == agentIdHash; (2) the ML-DSA signature is valid over the full record; and (3) commitmentHash actually commits to the stored ciphertext: sha256(ciphertext) == commitmentHash. Check (3) is required so that the SAIHM-operated on-chain anchor (which commits commitmentHash) is a sound tamper-evidence commitment to the bytes SAIHM stores — even against a signer that emits a validly-signed envelope whose commitmentHash lies about its ciphertext.
Verify a directory-fetched recipient record against an OUT-OF-BAND-pinned agentIdHash. (a) sha256(mldsaPubKey) MUST equal the pinned target — defeats DSA-key substitution; (b) the self-signature MUST bind mlkemPubKey to that DSA key — defeats KEM-key substitution. The self-sig ALONE proves only internal consistency; both checks are required.
Standalone sharer-signature check (the same check unwrapSharedDek enforces internally). Total predicate: a malformed/wrong-length public key or signature returns false, never throws.
No composition guidance. Descriptions do not explain tool ordering, dependencies, or which tools must be called before others. E.g., 'verifyIdentityRecord' requires 'pinnedAgentIdHash' but does not explain how to obtain it.
This is a library, not an MCP server. No transport, no tool registration mechanism, no protocol implementation visible. Cannot be deployed as a standalone MCP server without wrapping.