| Internet-Draft | SAIHM Protocol | October 2026 |
| Jackson | Expires 14 April 2027 | [Page] |
This document defines the Sovereign AI Horizontal Memory (SAIHM) protocol, a memory layer for AI agents. A SAIHM operator stores an agent's memory as encrypted cells bound to a post-quantum holder identity, records each state change in a public audit log, and supports revocable sharing contracts. The protocol defines an erasure operation with two stated erasure classes, intended to help an operator meet its obligations under Article 17 of EU Regulation 2016/679 (GDPR).¶
This revision specifies the custodial profile. The operator holds the holder's keys and performs all cryptography. The agent-side client holds no keys, runs no cryptography beyond TLS and keeps no store; it reaches the operator over HTTPS and exposes the protocol to the agent as eight Model Context Protocol (MCP) tools. The document defines the operator obligations, the HTTPS binding, the eight tools, and the checks a conformant client applies to what the operator returns.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 14 April 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document.¶
This document is published through the Independent Submission Stream of the RFC Series, as defined in [RFC4846] and described in [RFC8730]. It is not an Internet Standard, and it has not been reviewed for, nor does it necessarily reflect, the rough consensus of the IETF community.¶
The Model Context Protocol [MCP], donated to the Agentic AI Foundation [AAIF] under the Linux Foundation on 9 December 2025, defines how an AI agent reaches external tools and contextual data sources. It does not standardize how an agent persists memory across sessions, models, or vendors. Here, "memory" means the holder's content-addressed cells (Section 2.1; Section 1.4).¶
Production agents today keep memory in a local file system, a vendor-specific session log, or a vector database. None of these gives a signed record of each memory and of each change to it, a public log of those changes, or an erasure operation with stated properties ([GDPR], Article 17).¶
SAIHM is a memory-layer protocol that an MCP-capable agent attaches to obtain durable, shareable and erasable memory. Nothing in it depends on a particular operator or vendor.¶
This document specifies the custodial profile, in which the operator generates and holds the holder's identity key and every data-encryption key, encrypts on write, decrypts on read, signs cells and receipts on the holder's behalf, and returns plaintext to the authenticated holder.¶
The client in this profile forwards each tool call to the operator over HTTPS, holds no key material, and validates the shape of each response before reporting it to the agent (Section 7). Its security rests on the transport and on the operator, and Section 8 states what that does and does not protect.¶
A profile in which the holder keeps the keys and the operator sees only ciphertext is possible under the same cell shape. It is outside the scope of this revision.¶
This document defines:¶
This document does NOT define:¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
A conformant operator depends on the following. Where a substrate is selected per deployment, the operator states its selection in its published documentation.¶
A conformant client depends on an MCP server runtime, an HTTPS client, and a JSON parser ([RFC8259]). It needs no cryptographic library beyond the one its TLS stack uses.¶
A SAIHM cell is the tuple:¶
<cellId, holderId, kekVersion, tier, cellNonce, ciphertext, signature, timestamp>¶
32-byte content-derived identifier, computed as¶
cellId = SHA-256( kekVersion_be32 ||
cellNonce ||
ciphertext )
¶
where kekVersion_be32 is kekVersion as a 4-byte big-endian unsigned integer, ciphertext is the full AEAD output including the authentication tag, and || is octet concatenation. On the wire cellId is a lowercase hexadecimal string of 64 characters.¶
Because kekVersion and cellNonce are inputs, the same plaintext written twice yields two distinct cellIds.¶
When the operator reads a cell it MUST recompute cellId from the stored kekVersion, cellNonce and ciphertext, and MUST refuse to serve a cell whose stored cellId does not match.¶
A holder's identity key is derived from the holder seed:¶
identityKey = HKDF-SHA-256(salt = "MPS-PQC-KEY-GEN-v1",
IKM = holderSeed,
info = "MPS-AGENT-IDENTITY-v1",
L = 64 bytes)
¶
The first 32 bytes of identityKey are the seed (xi) for ML-DSA-65 key generation ([FIPS204], ML-DSA.KeyGen_internal). The remaining 32 bytes are reserved. holderId is SHA-256 of the resulting public key.¶
In the custodial profile the operator generates the holder seed, derives the key pair, and holds the private key. The private key and the holder seed MUST NOT be sent to the client, written to a receipt, or returned by any tool. The operator signs each cell and each receipt with the holder's private key after it has authenticated the caller as that holder (Section 3.2).¶
When it creates a holder, the operator MUST write the holder's public key to the audit log in a KEY receipt (Section 2.5), which anyone can find by holderId. The holder cannot be used until that receipt is written. With that entry, anyone can check every receipt signature for the holder without the operator's help, and anyone given a cell can check the cell signature.¶
Every signature in this protocol is ML-DSA-65 in its pure form (ML-DSA.Sign, [FIPS204]) with a context string that names what is signed, so that a signature over one kind of message cannot be presented as another:¶
An operator MAY use the hedged (randomized) or the deterministic signing variant.¶
For each new cell the operator:¶
The DEK MUST be independent of every other key the operator holds. In particular it MUST NOT be derivable from identityKey, from the holder seed, or from any other long-lived secret. Cryptographic erasure (Section 2.7) depends on this (Section 8.6).¶
The AEAD nonce (for AES-256-GCM, the 96-bit IV) is chosen by the operator and MUST NOT repeat under one DEK. Because each DEK encrypts one payload, using the first 12 bytes of cellNonce is sufficient, and the example in Appendix B.2 does so. The cell does not name its AEAD algorithm or the key-wrap algorithm; both are operator-internal and stated in the operator's documentation.¶
No associated data is used. The ciphertext is bound to the cell by cellId and by the cell signature, and the operator MUST check both before it decrypts (Section 4.3).¶
KEK rotation is versioned. An operator MAY rotate the KEK under its own policy and MAY re-wrap existing DEKs under the new generation. The wrapped DEK is stored with the version of the KEK that wrapped it, apart from the cell, so re-wrapping changes neither cellId nor the cell signature. An operator MUST re-wrap every DEK held under a KEK generation before it retires that generation.¶
For each operation that needs one (Section 6, and the KEY receipt of Section 2.2) the operator writes a receipt:¶
<receiptId, subjectId, operation, holderId, signature, timestamp, receiptNonce [, detail]>¶
subjectId is the cellId, contractId or proposalId the operation acted on, or the holderId for KEY, RECALL and STATUS receipts. operation is one of "KEY", "REMEMBER", "RECALL", "STATUS", "FORGET", "SHARE", "REVOKE", "PROPOSE" or "VOTE". A KEY receipt also carries the holder's ML-DSA-65 public key, a PROPOSE receipt carries the proposal, and a VOTE receipt carries the vote's direction and weight. Every receipt carries 16 random bytes, receiptNonce, so that two otherwise identical receipts have different receiptIds.¶
The receipt is written to a public, append-only audit log. The log MUST let any party fetch an entry by its receiptId and list entries by subjectId, by holderId and by operation (for example all VOTE receipts for a proposalId, a holder's KEY receipt, or every FORGET receipt), so that a third party holding a receiptId can check the receipt's signature against the holder's public key. An operator SHOULD return the receiptId in the result of every state-changing tool, as the member "receiptId"; saihm_forget returns it as destructionAnchor. The choice of log is a deployment decision.¶
saihm_forget erases one cell. Every conformant operator MUST, on a successful erasure:¶
An operator MUST consult the erasure list on every saihm_recall and MUST NOT return an erased cell to anyone.¶
Beyond those steps an operator provides one of two erasure classes:¶
Each operator MUST state in its published documentation which class it provides, and MUST NOT describe access-control erasure as cryptographic. Clients MUST NOT assert either class on the operator's behalf (Section 4.4).¶
Erasure does not remove the cell's receipts or its entry in the erasure list (Section 9.1).¶
For saihm_forget and saihm_revoke_share the operator MUST withdraw access before it writes the receipt, and MUST NOT restore access if the receipt write fails. If it cannot withdraw access it answers "operator_storage_unavailable" and nothing has changed. It retries the receipt until it is written. Until then it answers with the error "operator_log_unavailable", which here means that access has been withdrawn and the receipt is pending. A repeated saihm_revoke_share then returns revoked = true, and a repeated saihm_forget returns success = false (already erased). A client that receives this error SHOULD tell the agent that access may already be withdrawn and that repeating the call shows the outcome.¶
For the other state-changing tools the operator first stores the effect durably but invisibly, then writes the receipt, then makes the effect visible. If the effect cannot be stored or the receipt cannot be written, the operator discards the effect (no cell is stored, no contract or proposal takes effect, no vote is counted) and answers "operator_storage_unavailable" or "operator_log_unavailable"; for these tools both errors mean that no receipt was written and nothing took effect. If the operator cannot tell whether a receipt write succeeded, it MUST look the receipt up by its receiptId before answering, and treats a receipt it finds as written. Once the receipt is written the operator MUST complete the effect, including after a restart, and answers only with the success result carrying the result identifier.¶
The client sends each tool call to the endpoint as one HTTP POST ([RFC9110]) with these headers:¶
The request body is a JSON object with two members:¶
{"method": "<tool name>", "params": { ... }}
¶
method is one of the eight tool names in Section 4. params carries the operator parameters listed for that tool.¶
On success the operator answers with a 2xx status and a JSON body that is the tool's result object (or, for saihm_recall, an array). There is no outer envelope. On failure it answers with a 4xx or 5xx status; it SHOULD include a JSON body {"error": "<code>"} using the codes listed per tool. An operator that does not enable a tool (for example, governance) MUST answer that tool with a failure status.¶
A JSON integer that can exceed 2^53 - 1 (epochs, weights, counts) SHOULD be sent as a decimal string. A client MUST accept either a JSON number or a decimal string for every numeric field, and MUST accept true, false, "true", "false", 1, 0, "1" and "0" for every boolean field.¶
Fields not defined here are operator extensions. A client MUST ignore an extension it does not recognize, and an operator MUST NOT rely on a client displaying one.¶
Hexadecimal values are base16 ([RFC4648], Section 8) in lowercase. A cellId, holderId, contractId, proposalId or receiptId (including destructionAnchor) is 64 hexadecimal characters.¶
A client MAY report only the status code of a failure and not its body; the error codes below are for operators, logs and clients that read them.¶
The operator authenticates the caller from the Authorization header under a scheme it defines, for example a bearer token issued to one holder. The client treats the value as opaque and MUST NOT place private key material in it. The client does not sign requests; in the custodial profile the holder's signature on cells and receipts is made by the operator, after authentication, with the holder's custodied key.¶
A client:¶
The client exposes the protocol to the agent as eight MCP tools and no others:¶
The surface is fixed at eight within this revision. New contract modes, scopes or result fields are added by extending an existing tool's parameters or result. A future major revision MAY add or remove tools.¶
Each tool below gives its MCP input, the operator parameters the client sends, the operator result, the checks the client applies before reporting success, and the MCP tool annotations [MCP] the client declares. The result of every state-changing tool can also carry receiptId (Section 2.5); a client that prints it applies Section 4.1 to it.¶
cellId, contractId and proposalId are result identifiers. An operator returns them in the form given in Section 3.1. Whatever an operator sends, a client MUST reject any value that is not a JSON string of 1 to 256 characters in the range U+0021 to U+007E (printable ASCII, no space), wherever it would print the value, and MUST NOT truncate it.¶
The client prints these values as structure in its report to the agent, for example "FORGOTTEN [id] erased". An identifier that contains a line break could add a second, forged line to the report. A truncated identifier cannot be passed back to saihm_forget or saihm_revoke_share.¶
The client MUST reject the recall as malformed if the result is not an array, or if any element is not a JSON object (null, a primitive and an array all fail).¶
If the array is non-empty and no element carries a string plaintext, the endpoint returned sealed cells; the client MUST report that the operator is not custodial and that this client cannot read its memory. If some but not all lack plaintext, the client MUST report an incomplete recall, and MUST NOT attribute it to the custody model.¶
The client marks cells owned by another holder as described in Section 7.¶
If any element lacks a valid cellId the client MUST reject the recall, since those cells could not be erased.¶
An empty array after a non-empty query MUST NOT be reported as an empty store; the client says that nothing matched.¶
An agent cannot confirm an erasure by later inspection, so the client reports it only on a complete result. The client MUST:¶
an object. agentIdHashHex (the caller's holderId) is REQUIRED; a client that does not receive it reports the identity as not reported. A custodial operator SHOULD return custody = "custodial". The following fields are RECOMMENDED:¶
the start of the same window (UNIX epoch seconds) and R, the number of receipts in the audit log attributed to the holder in the window, which the holder can check by listing its receipts in the log. From these a holder that keeps its own log of tool calls computes the receipt-match index:¶
bfsi = 1 - (M / R)¶
where M is the number of those receipts with no matching call in the holder's log. When R = 0, bfsi is 1.0 by convention, which signals no evidence of unmatched receipts. The operator cannot see the holder's log, so it MUST NOT return bfsi or bfsi_M unless the holder has given it M by means outside this binding. The client in Appendix A keeps no such log.¶
A failure status from the operator means the vote was not counted (Section 2.8). When there is no response or the body is malformed, the client reports that it is unknown whether the vote was counted. Sending the vote again does not count it twice, because the operator returns the vote already recorded, and the mismatch check below reports a direction that differs from the request. For a well-formed response the client reports, in order of precedence:¶
This protocol defines the two governance tools but not a single governance form. An operator that enables governance MUST publish, at a stable URL, at least:¶
Tallies MUST be reproducible from the VOTE receipts in the audit log, counting, for each holder and proposal, only the first VOTE receipt in log order. Where two PROPOSE receipts carry the same proposalId, the first in log order governs.¶
A client MAY also expose an MCP prompt, which is not a tool and does not change the eight-tool surface, that tells the agent to call saihm_recall at the start of a session and states when to store, recall, share and erase. The implementation in Appendix A names it saihm_session_bootstrap.¶
Tool calls and results travel as JSON (Section 3). Cells and receipts at rest are CBOR ([RFC8949]). Timestamps in CBOR MUST be unsigned integers (major type 0).¶
A cell at rest (CBOR diagnostic notation):¶
{1: h'...cellId...',
2: h'...holderId...',
3: 1,
4: "STANDARD",
5: h'...cellNonce-16-bytes...',
6: h'...ciphertext...',
7: h'...signature...',
8: 1747526400}
¶
Keys: 1=cellId, 2=holderId, 3=kekVersion, 4=tier, 5=cellNonce, 6=ciphertext, 7=signature, 8=timestamp. The wrapped DEK is not part of the cell; the operator stores it separately, keyed by cellId, so that it can be destroyed without rewriting the cell.¶
A receipt (CBOR diagnostic notation):¶
{1: h'...receiptId...',
2: h'...subjectId...',
3: "REMEMBER",
4: h'...holderId...',
5: h'...signature...',
6: 1747526400,
8: h'...receiptNonce-16-bytes...'}
¶
Key 7, detail, is present only in KEY receipts (the holder's public key, as a byte string), PROPOSE receipts (a map {"scope": text, "paramKey": text / null, "proposedValue": text / null, "snapshotEpoch": uint}) and VOTE receipts (a map {"approve": bool, "weight": uint}).¶
A sharing contract (CBOR diagnostic notation):¶
{1: h'...contractId...',
2: h'...holderId...',
3: [h'...granteeId...'],
4: [h'...cellId...'],
5: "TEMPORARY",
6: "read",
7: 1747612800,
8: 1747526400,
9: h'...signature...',
10: h'...contractNonce-16-bytes...'}
¶
Keys: 1=contractId, 2=holderId, 3=granteeIds, 4=shardIds, 5=mode, 6=scope, 7=expiresAt, 8=timestamp, 9=signature, 10=contractNonce.¶
A saihm_status result (JSON):¶
{"agentIdHashHex": "ab4f746f...b1f588",
"custody": "custodial",
"prs": 0.997,
"bfsi_window_start_ts": "1745020800",
"bfsi_R": "0",
"shards": {"STANDARD": 42},
"contracts": [
{"contractId": "9c1e...",
"mode": "TEMPORARY",
"granteeIds": ["5d02..."],
"expiresAt": "1747612800"}],
"governance": []}
¶
For each call that changes state (saihm_remember, saihm_forget, saihm_share, saihm_revoke_share, saihm_governance_propose, saihm_governance_vote) the operator MUST write a receipt to the audit log carrying the operation, the subjectId, the holderId and the timestamp, signed with the holder's key (Section 2.3). A call that changes nothing, such as a repeated vote (Section 4.9) or a repeated saihm_forget or saihm_revoke_share, writes no receipt.¶
A receipt MUST NOT contain plaintext, a DEK, a wrapped DEK, a KEK, the holder seed or a private key. A FORGET receipt carries the erased cellId as its subjectId.¶
Read-only calls (saihm_recall, saihm_status) MAY produce receipts under operator policy, under the same content restrictions.¶
A conformant custodial client:¶
The agent reads the client's report and acts on it, and a report of an erasure, a revocation or a vote that did not happen as reported is hard to discover afterwards.¶
In the custodial profile the operator holds every key and sees every plaintext. Encryption at rest protects against theft of stored ciphertext and of backups, and against a storage provider separate from the operator. It does not protect a holder's content from the operator itself. That protection requires a profile in which the holder keeps the keys.¶
The holder's signatures are made by the operator. They establish that the operator, having authenticated the caller as that holder, wrote the cell or receipt. They do not prove that the holder's own machine produced it.¶
The operator MUST protect the holder seeds, identity keys and KEKs it holds at least as strongly as the plaintext they protect, and SHOULD keep KEKs in hardware or a separate key service.¶
Cells and receipts are signed with ML-DSA-65 ([FIPS204]), a NIST-standardized post-quantum signature algorithm, chosen so that receipts resist forgery by an adversary with a cryptographically relevant quantum computer. This does not extend to the operator's custody of the keys, or to the audit log's own integrity mechanism, whose algorithms are chosen by the deployment. TLS key exchange SHOULD include a post-quantum or hybrid group where both ends support one.¶
The Authorization value is a bearer credential for the holder's memory. A client MUST NOT echo it, or an endpoint value that may contain it, into tool output, including in errors raised by its HTTP library when the value is malformed (Section 3.3). Operators SHOULD issue credentials scoped to one holder, with an expiry, and revocable.¶
A client cannot assume the endpoint is the operator. A misconfigured URL, a proxy or an attacker on a weakened transport can answer instead. The checks in Section 7 limit what such a responder can make the agent believe. In particular, the result-identifier rule (Section 4.1), the control-character rule (Section 7) and the framing rule for recall plaintext (Section 7) together keep a single response from adding a forged report entry, the erasure rule prevents an acknowledgement without an identifier from being reported as an erasure, and the redirect rule keeps plaintext from leaving for an unchecked host.¶
A sharing contract places another holder's text in the grantee's recall, which an agent loads at the start of a session. Under Section 2.6 a holder can do this only after the grantee has accepted the contract, or where the operator has linked the two holders in advance. Accepted shared text is still written by another holder. The client marks it (Section 7), and an agent should not follow instructions found in it.¶
Cryptographic erasure holds only if no copy of the DEK, and nothing it can be derived from, survives. This is why the DEK is random and independent of identityKey (Section 2.4). An operator that derived DEKs from a long-lived secret could re-derive a deleted DEK and decrypt, so it would provide access-control erasure at most.¶
Plaintext the operator returned to the holder before erasure, and plaintext a grantee received, are outside the operator's control, and erasure does not affect them.¶
saihm_forget gives a holder an erasure operation with a receipt in a public log and an entry in a public erasure list. Whether it also makes retained ciphertext unreadable depends on the erasure class the operator states (Section 2.7). The protocol does not by itself make an operator compliant. Operators acting as controllers or processors under [GDPR] remain responsible for erasure without undue delay, including any plaintext in logs, caches and backups, and handle a request from a data subject who is not the holder outside this protocol.¶
Erasure leaves the cell's receipts and its erasure-list entry in place. The receipts carry the holderId and the erasure-list entry carries the cellId. Before choosing a public log, an operator assesses whether holderIds are personal data in its deployment and, if so, uses separate holderIds per context or a log whose entries it can restrict.¶
Because the operator decrypts, it also receives each recall query; draft-saihm-memory-protocol-01 kept the query on the holder's side. A query can reveal what the agent is working on even when it matches nothing. Operators SHOULD NOT log query text, and MUST NOT write it to a receipt.¶
Receipts are public. They show that a holderId performed an operation on a subject at a time, but not memory content. The existence and timing of a holder's cells are therefore observable. PROPOSE receipts disclose the proposal, VOTE receipts disclose each vote's direction and weight, and an operator that writes RECALL or STATUS receipts discloses when the holder reads. Where that is sensitive, further measures outside this protocol apply, such as separate holder identities per context.¶
This document has no IANA actions.¶
This appendix follows the practice of [RFC7942]. It records an implementation at the time of writing and is to be removed before publication as an RFC.¶
In version 0.3.17:¶
The first three are to be corrected in the next release.¶
Agent + client Operator Audit log
============== ======== =========
| | |
| POST saihm_remember | |
| {content}, Authz | |
|------------------------>| new DEK, nonce; seal; |
| | sign as holder; store |
| |------ REMEMBER --------->|
| {cellId, ...} | |
|<------------------------| |
| | |
| POST saihm_recall | |
|------------------------>| verify, decrypt, filter |
| [{cellId, plaintext}] | |
|<------------------------| |
| | |
| POST saihm_share | |
|------------------------>| sign contract |
| |------ SHARE ------------>|
| {contractId, ...} | |
|<------------------------| |
| | |
| POST saihm_revoke_share | |
|------------------------>|------ REVOKE ----------->|
| {revoked: true} | |
|<------------------------| |
| | |
| POST saihm_forget {id} | |
|------------------------>| withdraw; list cellId; |
| | destroy wrapped DEK * |
| |------ FORGET ----------->|
| {success, cellId, | |
| destructionAnchor} | |
|<------------------------| |
* cryptographic erasure class only (Section 2.7)
¶
These values come from a labeled test seed and MUST NOT be used for real cells. They can be reproduced with any FIPS 204 ML-DSA-65, RFC 5869 HKDF and AES-256-GCM implementation.¶
Test holder seed, SHA-256 of the 46-byte ASCII string given here on one line:¶
SAIHM-TEST-VECTOR-001-DO-NOT-USE-IN-PRODUCTION¶
holderSeed = f068b8db8484d33bdbedd154bf5bf28e11fba330b79469e23595d6f738d7f5c6¶
Identity key (Section 2.2):¶
identityKey = fdf786da8cb1f074393f9ad6dec1671e08085540e95c8463c9f2e15f437bbc5a 2c267aeaf355e79d8968cc21846cdff7e16f498437de3d6b1fd290703eeed9c2¶
holderId, SHA-256 of the ML-DSA-65 public key generated from the first 32 bytes of identityKey:¶
holderId = ab4f746fd1520d2736854559d6751969ae9127f5dbc607d7298acbf1afb1f588¶
kekVersion = 1. cellNonce and DEK, which in operation come from the random source, are fixed here so the example can be reproduced:¶
cellNonce = 25bd74b827789faacad8ffb7593c2359 DEK = a1e54f730c6c7a1048ae436c9d2b179821c6eddd7841f41e36215285f3ffd07a¶
The 41-byte plaintext, in hexadecimal (ASCII "Hello, SAIHM.", one space, "This is a test memory cell."), is encrypted with AES-256-GCM, IV = first 12 bytes of cellNonce, no associated data:¶
plaintext = 48656c6c6f2c20534149484d2e205468697320697320612074657374206d656d 6f72792063656c6c2e¶
ciphertext (57 bytes, including 16-byte tag) = 595b0b9f77a8d95f29c5affa151bbcc18fdc3f7e5223792627919ee2d52f2bf2 4933c35de4c1755559bee818ef54b48a388bcdd2e4a30fbde8¶
cellId (Section 2.1):¶
cellId = d851960a5b7754c5884c96bef5d615e666c8ad006e4ceebe028cd85aae8e7c2f¶
timestamp = 1747526400 (2025-05-18T00:00:00Z). Cell signature (Section 2.3): deterministic ML-DSA-65 with context "SAIHM-CELL-v1". It is 3309 bytes; the first 32 are:¶
signature[0..32) = 8b7466cd59b30c38cdacdf2a55fff14ba39dff905ad20a3fdffe9018f5bacf38¶
A hedged signature verifies the same way but is not reproducible.¶
A8 # map(8)
01 # key 1 (cellId)
58 20 # bytes(32)
d851960a5b7754c5884c96bef5d615e666c8ad006e4ceebe028cd85aae8e7c2f
02 # key 2 (holderId)
58 20 # bytes(32)
ab4f746fd1520d2736854559d6751969ae9127f5dbc607d7298acbf1afb1f588
03 # key 3 (kekVersion)
01 # unsigned(1)
04 # key 4 (tier)
68 # text(8)
5354414e44415244 # "STANDARD"
05 # key 5 (cellNonce)
50 # bytes(16)
25bd74b827789faacad8ffb7593c2359
06 # key 6 (ciphertext)
58 39 # bytes(57)
595b0b9f77a8d95f29c5affa151bbcc18fdc3f7e5223792627919ee2d52f2bf2
4933c35de4c1755559bee818ef54b48a388bcdd2e4a30fbde8
07 # key 7 (signature)
59 0CED # bytes(3309)
8b7466cd59b30c38cdacdf2a55fff14ba39dff905ad20a3fdffe9018f5bacf38
... (3277 further bytes)
08 # key 8 (timestamp)
1A 68292300 # unsigned(1747526400)
¶