did:me Method Specification (v1)
Status: Beta
Specification URI: https://did-me.org/spec/v1/
JSON-LD Context: https://did-me.org/ns/did-me/v1
Abstract
did:me is a self-certifying DID method for
individuals, personas, organizations, and groups. A did:me
identifier is derived from an immutable genesis commitment, while
current DID state is represented by signed, CID-addressed canonical
DAG-CBOR core snapshots and projected into a DID Core compatible
JSON DID Document. The method is designed for offline-verifiable
resolution, key rotation, optional public directory mirroring,
classical and post-quantum verification methods, EUDI wallet
interoperability, and privacy preserving presentation workflows.
1. Introduction
did:me is a DID method designed for:
- Stable identifiers for individuals, pseudonymous personas, organizations, and groups
- Identifiers derived from an immutable genesis commitment rather than from current keys or mutable state, ensuring DID stability across rekeying while binding each DID to exactly one genesis control commitment (its initial keys and update policy)
- CID-versioned core snapshots that define canonical DID
state
- Classical and post-quantum verification methods
- Compatibility with EU Digital Identity Wallets, OpenID4VCI / OpenID4VP, and privacy-preserving proof systems
The authoritative DID state is represented by a core object, encoded using canonical DAG-CBOR, hashed to a CID, and signed by the DID controller. The public DID Document is a JSON projection derived from this signed core.
did:me identifiers are fundamentally self-hosted
DIDs whose authoritative state lives on the controller’s wallet,
device, or agent. No central registry is required for validity or
resolution. Decentralization is enabled by the method’s
cryptographic update structure, including:
- Monotonic
sequencecounters for update ordering
prevlinks referencing the CID of the previous core- A verifiable, tamper-evident chain of signed core snapshots
1.1 Conformance
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 [RFC 2119] [RFC 8174] when, and only when, they appear in all capitals, as shown here.
Sections explicitly marked as non-normative, along with all examples and notes in this specification, are non-normative. Everything else in this specification is normative. Normative and informative references are listed in Section 19.
2. DID Format
did:me identifiers are not derived from
current keys, core CIDs, or any mutable DID state. They are
derived from an immutable genesis commitment that
includes controller-chosen entropy, allowing stable rekeying while
binding the DID string to exactly one genesis control commitment —
the initial controllerKeys, updatePolicy,
and nonce (Section 2.5). Because the genesis commitment
never changes, the identifier never changes — even as keys rotate or
services change — and no party can construct a competing,
validly-signed chain for the same identifier without controlling the
committed genesis material.
2.1 DID Syntax
A did:me identifier MUST conform to:
did:me:<identifier>
Where <identifier> is the full Bech32-encoded
string, including:
- the human-readable
part (HRP)
"me" - the separator
"1" - the 5-bit data payload
- the Bech32 checksum
The encoding is classic Bech32 as defined in
BIP-173 (checksum constant 1). It is
not Bech32m (BIP-350, checksum constant
0x2bc830a3); decoders MUST verify the BIP-173 checksum
and MUST reject identifiers that only validate under Bech32m.
The identifier MUST be entirely lowercase. Although Bech32
permits an all-uppercase variant, DID method-specific identifiers
are case-sensitive, and only the lowercase form is a valid
did:me identifier. Resolvers and validators MUST reject
identifiers containing any uppercase characters.
The 128-bit identifier payload is derived from the genesis commitment defined in Section 2.5:
Formal representation:
identifier = Bech32.encode(hrp="me", data=convertBits(payload[16], 8-to-5))
The checksum included in the Bech32 output MUST be preserved, as it provides error-detection and guards against accidental corruption.
2.2 Allowed characters
The data portion of the Bech32 identifier (after “me1”) MUST match Bech32’s lowercase character set:
^[023456789acdefghjklmnpqrstuvwxyz]+$
(Excludes: 1, b, i,
o.) For did:me v1, the data portion after
"me1" MUST contain exactly 32 characters: 26 data
characters derived from the 128-bit identifier payload and 6 Bech32
checksum characters.
2.3 Length
Given a 16-byte identifier payload:
- 16 bytes x 8 bits = 128 bits
- converted to 26 five-bit data characters
- Bech32 adds the
meHRP, separator1, and a 6-character checksum - the full Bech32 identifier string therefore has 35 characters
before the
did:me:prefix
2.4 Example
did:me:me1072e0a7myhee5da50wt3tu2a9sds44xv
This example is taken from the self-contained genesis binding vector in Section 17.6 (published with this specification at https://did-me.org/spec/v1/vectors/), and can be reproduced exactly using the algorithm in Section 2.5.
2.5 Identifier Derivation (Genesis Binding)
The 128-bit identifier payload is a cryptographic commitment to the DID’s initial control material. It is computed from a GenesisBinding object:
GenesisBinding = {
nonce: bytes(16), // controller-chosen 128-bit entropy
updatePolicy: CoreUpdatePolicy, // exactly as in the genesis core (Section 5.4)
controllerKeys: [CoreKey] // exactly as in the genesis core (Section 5.3)
}
Derivation steps:
- Generate a 16-byte
nonce, either from a cryptographically secure random number generator, or deterministically from a controller-held secret via a cryptographically strong KDF (e.g., HKDF with a per-DID info string), provided the result carries at least 128 bits of entropy and is unique per DID. The nonce is a public salt, not a secret, so deterministic seed-based derivation — which lets a wallet regenerate its identifier from a recovery seed — does not weaken the binding. - Construct the
controllerKeysarray andupdatePolicyobject that will appear in the genesis core (Section 5). - Encode the GenesisBinding object — a map containing exactly the three fields above and no others — using canonical DAG-CBOR as defined in Section 5.6.
- Compute:
payload = SHA-256( UTF8("did:me:v1:genesis") || bindingBytes )[0..15]
i.e., the first 16 bytes of the SHA-256 digest of the domain-separation tag concatenated with the canonical GenesisBinding bytes.
- Encode
payloadvia Bech32 with HRP"me"as described in Section 2.1.
The controllerKeys and updatePolicy
values in the GenesisBinding MUST be identical — element for element
and field for field, including array element order
— to the controllerKeys and updatePolicy
values in the genesis core (sequence = 1). The GenesisBinding
deliberately excludes id (which would be circular),
controller, services, the relationship
arrays, and all signatures, so that the identifier commits to the
initial cryptographic control material and nothing else.
Because the identifier is a commitment over these values, every array that participates in the commitment MUST use a deterministic order, so that the reconstruction in Section 2.6 reproduces the exact bytes regardless of how the core was authored:
- In both the GenesisBinding and the genesis core,
controllerKeysMUST be ordered by ascendingid(compared as UTF-8 byte sequences). updatePolicyMUST be copied into the GenesisBinding verbatim, preserving the element order ofallowedVerificationMethodsexactly as it appears in the genesis core.
A GenesisBinding whose controllerKeys order differs
from the genesis core — even when the set of keys is identical —
produces a different identifier and MUST be treated as
non-conformant. (An earlier draft did not pin this order;
implementations MUST use the ascending-id order defined
here.)
The identifier therefore binds the DID to exactly one genesis
control commitment, not to every field of the
genesis core: genesis cores that differ only in fields outside the
commitment (e.g., controller, services, or
relationship arrays) derive the same identifier. Since only the
holder of the committed key material can validly sign such variants,
this is a self-equivocation case, not a takeover vector; resolvers
disambiguate it with the same fork-rejection and first-seen pinning
rules that apply to any conflicting snapshots (Sections 11.2 and
11.3).
The nonce prevents the identifier from being a naked
fingerprint of public keys (two DIDs created with the same key
material still receive unlinkable identifiers), while the hash
binding prevents any party who does not control the committed
genesis material from presenting a competing genesis for an existing
identifier.
2.6 Identifier Verification
Given a did:me identifier and a candidate genesis
core, verifiers MUST:
- Extract
nonce,updatePolicy, andcontrollerKeysfrom the genesis core, takingcontrollerKeysin the core’s canonical ascending-idorder (Section 2.5). - Reconstruct the GenesisBinding object — the map
{ nonce, updatePolicy, controllerKeys }— and encode it using canonical DAG-CBOR (Section 5.6). - Recompute the identifier payload per Section 2.5 and Bech32-encode it.
- Confirm the result is byte-identical to the identifier in the DID.
Because the ordering of every committed array is fixed by Section 2.5, this reconstruction is fully deterministic: any conformant verifier derives the same identifier from the same genesis core.
A genesis core whose recomputed identifier does not match MUST be rejected, and any chain built on it MUST be rejected. Section 3.4 defines the assurance levels available when a resolver holds only the current core rather than the full chain.
3. DID Resolution
3.1 Optional Directory-Based Resolution
Controllers MAY publish their DID Document to one or more public directories (e.g., directory.example). If published, a DID Document can be retrieved via:
GET https://directory.example/dids/<id>
This endpoint is optional and not required for DID validity or resolution. Local resolvers MAY reconstruct DID state using only the signed core chain.
Directories SHOULD serve DID Documents with the media type
application/did+ld+json. Directories MAY additionally
serve prior core snapshots by CID, enabling full-chain verification
(Section 3.4):
GET https://directory.example/cores/<cid>
When served, historical core snapshots MUST be returned as the
raw canonical DAG-CBOR bytes (media type
application/octet-stream) or as a base64url-encoded
JSON wrapper; in either case the bytes MUST hash to the requested
CID.
3.2 HTTP Status Codes
200 OK— DID Document returned
404 Not Found— DID Document not found in that directory410 Gone— DID has been deactivated (Section 8.3); the response SHOULD include resolution metadata with"deactivated": true
Directories MUST NOT return 404 Not Found for a DID
they know to be deactivated; deactivation and absence are distinct
states, and conflating them enables a malicious or stale mirror to
silently serve a pre-deactivation document (see Section 11.6).
3.3 DID URL Parameters
Resolvers that maintain or can retrieve the core chain SHOULD support the following DID Core resolution parameters:
versionId— the CID of a specific core snapshot. The resolver returns the DID Document projection of that snapshot, withdidDocumentMetadata.versionIdset to the CID. Requests for a CID not in the DID’s verified chain MUST fail withnotFound.versionTime— resolvers that record observation times for snapshots MAY mapversionTimeto the latest snapshot observed at or before the given time. Because core snapshots do not embed timestamps,versionTimesupport is OPTIONAL and resolver-specific.
Resolution results SHOULD populate
didDocumentMetadata with versionId (the
currentCore CID), and deactivated: true
where applicable.
3.4 Verification Assurance Levels
did:me resolution supports two assurance levels:
- Attestation-verified (self-contained). Using
only the DID Document (which carries
coreCborin the default profile), a verifier confirms thatcoreCborhashes tocurrentCoreand that at least the required attestations verify over the core bytes (Section 7.1). This proves the document reflects a core signed by keys listed in that core, but does not by itself prove the chain back to genesis. - Chain-verified (genesis-bound). The verifier
retrieves each prior core by CID (via
prevlinks), validates every transition per Section 8.2, and verifies the identifier binding of the genesis core per Section 2.6. This provides the full method-level guarantee: the DID string is cryptographically bound to this exact chain.
For a genesis-state DID (sequence = 1), the two levels coincide: the DID Document is fully self-verifying, including the identifier binding. Verifiers SHOULD perform chain verification when the sequence is greater than 1 and the trust context warrants it (e.g., first encounter with a DID, high-value transactions, issuer onboarding); they MAY cache the verified (genesis CID, currentCore, sequence) tuple and subsequently verify only new transitions.
4. Data Model Overview
A did:me DID Document is a standard DID Document
projected from a signed core object and:
- Uses
verificationMethod,authentication,assertionMethod,capabilityInvocation,keyAgreement, andserviceas defined in DID Core. - Adds the following method-specific fields:
sequence— monotonic per-DID update counter
prev— CID of the previous core; omitted from the DID Document projection for genesis statenonce— controller-chosen 128-bit entropy in the genesis core; part of the identifier binding (Section 2.5)currentCore— CID of the current DAG-CBOR core object
keyHistory— ordered list of prior core CIDsupdatePolicy— rules defining which verification methods may authorize updates, including an optional signaturethresholdattestations— signatures over the core object by the DID controller
proof— optional Data Integrity Proof overcurrentCore
domainVerification— optional DNS/HTTP binding the DID to a domain
4.1 Method-Specific Properties
did:me supports method-specific properties that
extend the base DID Document data model. These properties are
defined in the did:me JSON-LD context:
https://did-me.org/ns/did-me/v1
The context defines terms such as:
- currentCore
- keyHistory
- attestations
- updatePolicy
- domainVerification
- and additional optional metadata fields (e.g., hardwareBound, biometricProtected, deviceModel)
Method-specific terms fall into two categories:
- core-derived terms such as
sequence,prev,nonce,currentCore,coreCbor,keyHistory, andupdatePolicy, which affect did:me validation according to this specification attestations, which are not part of the canonical core object but are authoritative evidence for validating the core snapshot- optional extension metadata such as
domainVerification,proof,hardwareBound,biometricProtected,userVerificationMethod, anddeviceModel, which MUST NOT alter the canonical core object or update authorization unless a future profile explicitly includes them in the core schema
Generic DID processors that do not understand optional extension
metadata MUST ignore that metadata, per DID Core rules. did:me
processors MUST NOT ignore core-derived did:me terms or
attestations when validating a did:me DID Document.
These properties allow did:me to support richer metadata while preserving DID Core compatibility and forward extensibility.
4.2 Encoding Preferences
did:me uses DID Core-compatible key representations.
Public verification keys use the representation required by their
verification method, such as publicKeyMultibase;
signature values and binary proof payloads such as
attestations.sig, proof.jws, and
coreCbor use base64url where this specification
requires it.
4.3 Protocol buffers
Due to the size of post-quantum keys, implementations MAY use a Protocol Buffers serialization as an implementation detail for transport or storage. This specification does not define a normative Protocol Buffers mapping.
The canonical authoritative state is always the signed canonical DAG-CBOR core:
- CID derivation MUST be performed from canonical DAG-CBOR core bytes
- authoritative signature verification MUST be performed over canonical DAG-CBOR core bytes
The DID Document JSON is a projection output derived from the core. Protocol Buffers bytes MUST NOT be used for CID derivation or authoritative signature verification.
5. Core Object
5.1 Purpose
The core object is the canonical representation of DID state. It is:
- encoded using canonical DAG-CBOR
- hashed to a CID (CIDv1, SHA-256, base32)
- signed by the DID controller
- used to derive the public JSON DID Document (“projection”)
Although the DID string never changes, all mutable cryptographic state is reflected through successive, signed core snapshots.
currentCore contains the CID of the latest snapshot.
keyHistory contains only the CIDs of prior core
snapshots, in ascending sequence order. prev references
the immediately preceding core CID and enforces canonical update
ordering.
prev enables:
- rollback prevention (reject older snapshots)
- replay protection (reject repeated snapshots)
- fork detection (reject alternative update branches)
This design provides:
- deterministic state integrity
- tamper-evident version history
- key rotation without changing the DID
- future support for decentralized or mirrored resolvers
The keyHistory array MUST contain only the CIDs of
all prior core snapshots and MUST NOT include the
currentCore CID. The CIDs in keyHistory
MUST be ordered in strict ascending sequence order, representing the
canonical update history from the first snapshot to the most recent
prior snapshot.
5.2 Core Schema (Conceptual)
Core = {
id: string, // "did:me:<id>"
sequence: uint64, // monotonic update counter. This always starts at 1.
prev: CID, // CID of previous core. Present iff sequence > 1.
nonce: bytes(16), // identifier-binding entropy. Present iff sequence == 1.
controller: string | string[], // one or more controllers
controllerKeys: [CoreKey], // declared keys
authenticationKeys: [string], // references into controllerKeys
assertionKeys: [string],
keyAgreementKeys: [string],
services: [CoreService], // service endpoints
updatePolicy: CoreUpdatePolicy, // defines which verification methods may authorize updates
}
This schema represents exactly the content protected by the signature. Anything not included here (e.g., proof, domainVerification) is not part of the core and does not affect DID validity.
Field presence rules are strict, because any variation changes the canonical bytes and therefore the CID:
- For genesis cores (
sequence == 1): theprevkey MUST be entirely absent from the encoded CBOR map (not present with a null value), and thenoncekey MUST be present as a 16-byte CBOR byte string. - For all subsequent cores (
sequence > 1): theprevkey MUST be present with the CID of the immediately preceding core, and thenoncekey MUST be entirely absent. - All other schema fields MUST be present in every core. Empty relationship or service lists are encoded as empty CBOR arrays.
A did:me DID MAY have one or more controllers. If only one
controller is present, it is typically the DID subject itself. All
keys in controllerKeys are controlled by the DID
controller set unless a future profile defines per-key controller
ownership semantics.
5.3 CoreKey Schema
All of the following are required values:
CoreKey = {
id: string, // e.g., "#ed25519"
type: string, // "Multikey" for all did:me v1 keys (Section 5.3.1)
algorithm: string, // key-type identifier, e.g., "Ed25519", "P-256", "ML-DSA-87", "X25519", "ML-KEM-768"
publicKeyMultibase: string
}
Keys listed here define:
- which verification methods exist
- which methods can sign updates (via updatePolicy)
- which methods serve authentication, assertion, invocation, or key agreement
All key relationship references (authenticationKeys, assertionKeys, keyAgreementKeys, and allowedVerificationMethods in updatePolicy) MUST refer to a CoreKey.id present in controllerKeys. Section 15 defines a stricter, normative default profile for public did:me v1 interoperability; those profile requirements may constrain this base model further.
5.3.1 Public Key Encoding (Normative)
publicKeyMultibase values MUST be encoded as
Multikey: the multicodec varint prefix for the key
type, followed by the raw public key bytes, encoded with multibase
base58btc (prefix z). The following multicodec codes,
as registered in the multicodec table, are normative for did:me
v1:
| Algorithm | Multicodec name | Code | Raw key bytes |
|---|---|---|---|
| Ed25519 | ed25519-pub |
0xed |
32 |
| X25519 | x25519-pub |
0xec |
32 |
| P-256 (ES256) | p256-pub |
0x1200 |
33 (SEC1 compressed) |
| secp256k1 | secp256k1-pub |
0xe7 |
33 (SEC1 compressed) |
| ML-DSA-44 | mldsa-44-pub |
0x1210 |
1312 (FIPS 204) |
| ML-DSA-65 | mldsa-65-pub |
0x1211 |
1952 (FIPS 204) |
| ML-DSA-87 | mldsa-87-pub |
0x1212 |
2592 (FIPS 204) |
| ML-KEM-768 | mlkem-768-pub |
0x120c |
1184 (FIPS 203) |
| ML-KEM-1024 | mlkem-1024-pub |
0x120d |
1568 (FIPS 203) |
Elliptic-curve public keys (P-256, secp256k1) MUST use the SEC1
compressed point form. Implementations MUST reject a
publicKeyMultibase value whose multicodec prefix does
not match the declared algorithm, or whose decoded key
length does not match the table above. The genesis conformance
vector (Section 17.6) carries a worked
publicKeyMultibase (Multikey) encoding for each
supported algorithm.
Some of these multicodec entries (notably the ML-DSA and ML-KEM
codes) currently carry draft status in the upstream
multicodec table. did:me v1 pins the code values above normatively:
they remain the did:me v1 encodings even if the upstream table later
changes an entry’s status, name, or value. Any future change would
require a new did:me method version or profile.
5.4 CoreUpdatePolicy
The update policy is part of the canonical core object and defines which verification methods are authorized to sign core updates.
CoreUpdatePolicy = {
allowedVerificationMethods: [string], // references into controllerKeys by id. These strings MUST match a CoreKey.id.
threshold?: uint // minimum number of valid attestations required to authorize an update.
// OPTIONAL; when absent, the threshold is 1.
}
When present, threshold MUST be an integer greater
than or equal to 1 and less than or equal to the number of entries
in allowedVerificationMethods; cores violating this
constraint are invalid and MUST be rejected. When the
threshold key is absent it MUST be entirely absent from
the encoded CBOR map, and the effective threshold is 1. Threshold
semantics for update validation are defined in Section 8.2.1.
allowedVerificationMethods MUST be non-empty in
every active core. An empty allowedVerificationMethods
array (with the threshold key absent) is permitted only
in a terminal deactivation core (Section 8.3), where it means that
no verification method can ever authorize a subsequent update.
A threshold of 2 or more provides true hybrid AND-control: for
example, listing an ML-DSA-87 and an Ed25519 verification method
with threshold: 2 requires both a post-quantum and a
classical signature on every update, so an attacker must break both
schemes (or compromise both keys) to forge a state transition.
5.5 CoreService Schema
CoreService = {
id: string, // "#hub", "#openid", "#wallet"
type: string, // DID Core service type or did:me extension
serviceEndpoint: any // URI or structured JSON
}
Service endpoints are part of the core snapshot and therefore immutable within each version.
5.6 Canonical CBOR Encoding
The core object MUST be encoded using canonical DAG-CBOR:
- Map keys sorted length-first, then bytewise: a shorter UTF-8 key sorts before a longer key; keys of equal length are compared lexicographically by their UTF-8 bytes. This is the canonical map-key ordering required by the DAG-CBOR specification, and for text keys it is equivalent to the RFC 8949 deterministic-encoding rule of sorting by the bytewise order of the keys’ encoded forms (the encoded form leads with the length).
- Only definite-length arrays/maps
- UTF-8 text keys
- Integers encoded in their shortest form
- No undefined or extraneous fields; conditional fields
(
prev,nonce,threshold) are entirely absent rather than null when not applicable - Deterministic encoding per RFC 8949 core deterministic encoding requirements and DAG-CBOR restrictions
For example, the top-level core map keys are encoded in the
order: id, nonce (genesis) or
prev (updates), sequence,
services, controller,
updatePolicy, assertionKeys,
controllerKeys, keyAgreementKeys,
authenticationKeys.
This ensures:
- identical encoding across languages
- identical CIDs across implementations, including standard IPLD DAG-CBOR libraries
- provable state integrity
Implementations MUST reject cores containing indefinite-length items or map keys not in the canonical order defined above. Note that a plain lexicographic sort of the raw key strings (ignoring length) produces a different ordering and therefore different CIDs; implementations MUST NOT use it.
5.7 CID Derivation
The CID is computed as follows:
- Codec:
dag-cbor
- Multihash:
SHA-256
- CID version: v1
- Multibase: base32 (lowercase)
This CID is published as:
"currentCore": "<cid>"and used to verify signatures over the core.
5.8 Payment and Crypto Address Services
did:me supports chain-agnostic crypto payment addresses through standard DID Core service entries. These addresses are non-authoritative metadata and are not part of the core update authorization model unless the corresponding public keys also appear in controllerKeys.
A DID MAY include one or more payment-related services such as:
- Bitcoin (Bech32)
- Ethereum and EVM chains (0x-prefixed addresses)
- Solana (base58)
- Avalanche (X-Chain, C-Chain, or P-Chain formats)
- Any other blockchain or digital payment network
Payment addresses MUST be expressed as a structured JSON serviceEndpoint object, for example:
{
"id": "#wallet",
"type": "PaymentService",
"serviceEndpoint": {
"btc": "bc1q...",
"eth": "0x1234...",
"sol": "So1111...",
"avax": "0xabc..."
}
}
5.9 EU Digital Identity Wallet Interoperability
did:me is designed to interoperate cleanly with the European Digital Identity Wallet (EUDI Wallet) architecture, including PID, ARF, and EBSI profiles. DID Documents MAY include service endpoints and proof formats that support credential issuance, presentation, and trust-chain interoperability.
5.9.1 EUDI Wallet Subject Compatibility
did:me identifiers:
- are stable identifiers bound to an immutable genesis commitment rather than to current key material
- support rekeying without subject identifier changes
- are fully compatible as subject_id values in PID / ARF data models
- remain resolvable even when controller cryptographic material rotates
5.9.2 OIDC4VCI and OIDC4VP Service Endpoints
did:me supports EUDI wallet credential flows via standard DID Core service entries:
{
"id": "#openid-credential-offer",
"type": "OpenID4VCI",
"serviceEndpoint": "<issuer-endpoint>"
}
{
"id": "#oid4vp",
"type": "OpenID4VP",
"serviceEndpoint": "<presentation-endpoint>"
}
Resolvers MAY treat these endpoints as wallet-compatible credential interfaces.
5.9.3 Optional Service Type for EUDI Integration
Implementations MAY define a dedicated service entry for EUDI wallet interactions:
{
"id": "#eudi",
"type": "EudiCredentialService",
"serviceEndpoint": "<eudi-service-url>"
}
This is functionally equivalent to using OpenID4VCI and OpenID4VP, but provides clearer semantic typing for wallets that recognize EUDI-specific services.
5.9.4 Relationship to X.509 Trust Chains and SD-JWT VC / VP (Non-Normative)
The eIDAS 2.0 / EUDI architecture does not require DIDs: its primary trust rails are X.509 certificate chains anchored in trusted lists, with SD-JWT VC and ISO mdoc as credential formats and OpenID4VCI / OpenID4VP as protocols. did:me is designed to complement these rails rather than replace them, in three ways:
Holder and subject binding. A did:me identifier
can serve as the stable credential subject across wallet migrations
and key rotations. SD-JWT VC holder binding (the cnf
claim) can carry a JWK corresponding to a key published in the
did:me document — the #p256 assertion key is a P-256
key that can produce ES256 signatures, so the same WSCD- or Secure
Enclave-resident key can back both the SD-JWT key-binding JWT and
the DID verification method. During presentation, the Key Binding
JWT is signed with that key, and a verifier can confirm the key
against the DID Document (including fully offline, via the embedded
coreCbor).
Issuer identity and X.509 bridging. An issuer
can anchor its did:me identity to its eIDAS-recognized legal
identity by dual binding: an X.509 end-entity certificate (issued
under a trusted-list chain) that certifies the same P-256 key listed
in controllerKeys, and/or carries the DID in a
subjectAltName URI entry, while the DID Document
carries the corresponding domainVerification entry in
the other direction. In this pattern the X.509 chain provides the
eIDAS-recognized trust path, and the did:me chain provides what
certificates alone do not: key continuity across certificate
renewals, a stable issuer identifier independent of any single CA
relationship, and post-quantum attestations over the issuer’s key
state. The DID persists as the root of continuity; the
certificate chain supplies the root of legal
recognition.
Post-quantum migration bridge. The EUDI ecosystem’s deployed rails are classical (ES256 throughout). A did:me identity carries an ML-DSA-87-anchored control plane underneath those classical interfaces today, so issuers and holders gain post-quantum key-state continuity now, and can adopt future eIDAS PQC credential profiles without changing identifiers, subject bindings, or trust relationships.
5.10 Wallet Binding and Domain Verification
DID Documents MAY include domainVerification entries used for:
- binding a DID to a legally recognized domain
- proving issuer authenticity
- supporting EUDI trust chain requirements
These domain bindings are optional metadata and do not alter core state.
5.11 Secure Messaging Pre-Key Discovery
did:me supports asynchronous secure-messaging bootstrap by publishing medium-term key-agreement public keys — messaging pre-keys — in the DID Document, discovered through a dedicated service entry. This replaces server-held pre-key distribution: the pre-key a sender uses is signed into the controller’s core chain, so every sender resolves the same key material, and a directory or delivery server cannot substitute per-sender keys.
This section does not define a messaging protocol. It defines authenticated discovery of controller-published hybrid key-agreement material that can be consumed by direct asynchronous messaging protocols, MLS-based messaging protocols, or future messaging profiles.
A DID Document MAY include a messaging service entry:
{
"id": "#messaging",
"type": "MessagingService",
"serviceEndpoint": {
"uri": "https://relay.example.com/inbox/7f3a",
"preKeys": ["#x25519-msg", "#mlkem768-msg"]
}
}
uriis the delivery or bootstrap endpoint where initial messages, MLS Welcome messages, MLS KeyPackage discovery requests, or other protocol-specific bootstrap artifacts for this controller can be deposited or retrieved. Its transport semantics are defined by the messaging protocol, not by this specification.preKeysdesignates which published key-agreement keys serve as messaging pre-keys. Each entry MUST reference a verification method id that appears in the DID DocumentkeyAgreementrelationship (and therefore corresponds to a CoreKey referenced bykeyAgreementKeys).- A pre-key set used for asynchronous session establishment MUST reference at least one X25519 key and at least one ML-KEM-768 or ML-KEM-1024 key. Published pre-keys are long-lived public artifacts, so a classical-only pre-key exposes recorded traffic to harvest-now-decrypt-later attack. How the classical and post-quantum shares are combined into a hybrid initial secret is defined by the messaging protocol that consumes the keys (Section 16, keyAgreement).
- A controller MAY publish more than one pre-key of the same algorithm concurrently. When several are published, senders SHOULD select among them uniformly at random: distributing initiations across concurrent pre-keys compartmentalizes the exposure from any single pre-key compromise and smooths rotation.
Infrastructure authority. Messaging infrastructure MAY relay, cache, advertise, or transport protocol-specific bootstrap artifacts. It MUST NOT be authoritative for cryptographic identity or key-agreement public keys. A sender MUST treat X25519 and ML-KEM messaging keys as authoritative only when they are bound to the signed did:me core snapshot it resolved.
Hybrid combiner selection. The choice between ML-KEM-768 and ML-KEM-1024 belongs to the controller; both satisfy the pre-key set requirement above. When the selected pre-keys are an X25519 key and an ML-KEM-768 key, initiating protocols SHOULD combine them with the X-Wing hybrid KEM [XWING] rather than an ad hoc construction: X-Wing is specifically defined for X25519 and ML-KEM-768, and its combiner and domain-separation label are designed and security-analyzed for exactly this pair. When ML-KEM-1024 is selected for a higher post-quantum security category, protocols MUST NOT describe the resulting construction as X-Wing unless a compatible standardized X-Wing variant exists; they MUST use a KDF-based combiner that binds both shared secrets, both ciphertexts or public keys, and a protocol-specific domain-separation label, and SHOULD migrate to a standardized combiner when one becomes available.
Rotation and retention.
- Messaging pre-keys are medium-term keys. Controllers rotate them with an ordinary core update (Section 8.2); the DID string does not change.
- Controllers SHOULD rotate messaging pre-keys on a fixed cadence. The rotation interval bounds the initial-message forward-secrecy window (Section 11.9). Rotating on a fixed schedule, rather than in response to events, also avoids turning rekey timing into an operational signal (Section 12.6).
- After publishing a replacement pre-key, controllers SHOULD retain the superseded private key for a bounded grace window, so that initial messages prepared against the immediately previous snapshot remain decryptable, and MUST destroy it when the window ends. On suspected compromise, controllers SHOULD rotate immediately and destroy the compromised private key without a grace window.
- Senders SHOULD resolve the controller’s current DID Document immediately before initiating a new session rather than initiating from a cached document.
Transcript binding. Initiating protocols SHOULD
bind the DID, the core sequence, and the core CID of
the snapshot that supplied the pre-keys into the handshake
transcript (for example, as associated data). Both parties then
cryptographically agree on which published key state was used, and
stale-snapshot or downgrade conditions become detectable.
Deniability. Transcript binding is associated
data, not an identity signature. Where conversation deniability is a
goal, initiating protocols SHOULD authenticate the handshake
implicitly through the key agreement itself rather than by signing
handshake transcripts with authentication or
assertionMethod keys, since an identity-key signature
over a conversation transcript is transferable cryptographic
evidence that the conversation took place.
Direct and MLS messaging. Pre-key discovery as
defined here can bootstrap direct asynchronous messaging sessions or
MLS-based conversations. Direct messaging profiles consume the
DID-published pre-keys directly. MLS profiles bind MLS credentials
and KeyPackages to did:me identifiers — for example, an MLS
credential carrying the DID whose published
authentication key signs the member’s leaf node. MLS
KeyPackages are one-time, consumable objects; they are delivered
through the messaging delivery layer, not published in the DID
Document. The DID Document’s role in MLS is to authenticate the
identity and key state behind each KeyPackage, not to carry MLS group
state.
Like other service entries, a MessagingService is part of the core snapshot (Section 5.5): it is immutable within a core version, and changing the delivery endpoint or the pre-key designation requires a signed core update.
6. Data Integrity Proofs (Optional P-256 Anchor)
6.1 Purpose and Motivation
did:me supports optional
DataIntegrityProof objects using ES256 (P-256).
These proofs provide a classical-cryptography signature anchor suitable for:
- EU Digital Identity Wallet trust infrastructure
- privacy-preserving proof workflows that can consume compact commitments
- verifiers that require standardized P-256 semantics
- environments that cannot process canonical DAG-CBOR
These proofs are non-authoritative: the authoritative cryptographic rail is the signature over the canonical DAG-CBOR core snapshot.
DI proofs:
- MAY appear in a DID Document
- MUST NOT influence update authorization or determine DID validity
6.2 Proof Object Schema
A did:me DataIntegrityProof using the
es256-jws-cid-2025 cryptosuite appears as follows:
"proof": {
"type": "DataIntegrityProof",
"cryptosuite": "es256-jws-cid-2025",
"proofPurpose": "assertionMethod",
"verificationMethod": "#p256",
"created": "<ISO8601Z>",
"jws": "<compact-jws-with-currentCore-as-payload>"
}
es256-jws-cid-2025 is a did:me-defined custom Data
Integrity cryptosuite profile. Implementations MUST apply the
verification algorithm defined in this specification for this
cryptosuite value.
The proof MUST:
- sign the UTF-8 byte sequence of the currentCore CID string
- NOT sign or canonicalize any other part of the DID Document
- use a verificationMethod listed in assertionMethod
6.3 Cryptosuite: es256-jws-cid-2025
The cryptosuite identifier:
"cryptosuite": "es256-jws-cid-2025"
The es256-jws-cid-2025 suite defines a compact ES256 JWS signature over the currentCore CID string. This cryptosuite is a did:me custom profile and is not required to be supported by generic Data Integrity processors unless they implement this suite definition.
Components:
- Signature type: ECDSA secp256r1 (P-256)
- Representation: compact JWS (header.payload.signature)
- Protected header: The protected header MUST contain exactly the single member {“alg”:“ES256”}.
- Payload: the UTF-8 byte sequence of the currentCore CID string
- Signing input: BASE64URL(header) + “.” + BASE64URL(payload)
- Signature format: JWS ES256 signature bytes in
fixed-length
R || Sform (64 bytes total, base64url-encoded in the JWS signature segment)
Verification Steps:
- Parse the compact JWS into header, payload, signature.
- Confirm the protected header contains exactly the single member
{"alg":"ES256"}. - Confirm the
verificationMethodreferenced by the proof is listed in the DID Document’sassertionMethodrelationship, per Section 6.2. - Decode payload and confirm it equals
currentCore. - Recompute
signingInput. - Verify ECDSA P-256 over SHA-256(
signingInput) using the referenced P-256 key.
The created timestamp is informational: verifiers
MUST NOT treat a proof as invalid solely because of its
created value, though trust frameworks MAY layer their
own freshness policies on top.
This proof MUST NOT be used to authorize DID updates. If a
verifier does not support es256-jws-cid-2025, it MUST
treat this proof as unsupported and MUST NOT treat it as valid.
6.4 ECDSA S-Value Canonicality
ES256 signatures MAY use either the “low-S” or “high-S” form. Both are cryptographically valid and MUST be accepted.
Implementations:
- SHOULD produce low-S signatures when signing (s <= n/2), since some third-party verifiers outside this specification’s control reject high-S forms
- MAY normalize to low-S (s <= n/2)
- MUST NOT reject high-S signatures
- MAY apply normalization internally (s := min(s, n − s))
This rule ensures full interoperability across platforms such as Secure Enclave, WebCrypto, and noble-curves.
6.5 Interoperability and Usage Notes
- DI proofs are optional metadata and do not affect DID validity or update logic.
- Resolvers MUST ignore DI proofs when determining the authoritative core chain.
- Relying parties MAY use DI proofs for assurance or trust-framework requirements.
- DI proofs MAY be safely added, omitted, or mirrored without affecting core state.
- The suite is designed to interoperate cleanly with EUDI Wallets, OpenID4VCI/VP, and proof systems that can consume compact CID commitments. P-256 is included primarily for hardware and standards compatibility, not because ECDSA over P-256 is inexpensive to prove inside zero-knowledge circuits.
7. Signatures and Attestations
7.1 Core Signature
Each core snapshot MUST be signed by the DID controller using verification methods listed in:
updatePolicy.allowedVerificationMethods
with at least updatePolicy.threshold (default 1)
distinct methods contributing valid signatures (Section 8.2.1).
For updates, the signature authorizing the transition is verified
against the previously active updatePolicy and
controllerKeys. After the update is accepted, the new
core snapshot’s updatePolicy governs subsequent
updates.
The signature covers a domain-separated signing input constructed from the canonical DAG-CBOR bytes of the core object. The domain-separation tag is the 14-byte UTF-8 string:
DOMAIN_TAG = UTF8("did:me:v1:core")
The signing input is:
coreSigningInput = DOMAIN_TAG || coreBytes
Per-algorithm signing input:
- ML-DSA-87:
ML-DSA.Sign(sk, M = coreSigningInput)using the default/empty FIPS 204 context unless a future profile explicitly defines a different context. - Ed25519:
Ed25519.Sign(sk, coreSigningInput). - ECDSA (ES256 / P-256, or ES256K / secp256k1 — permitted
only in private profiles): ECDSA over
SHA-256(coreSigningInput).
Domain separation ensures a did:me core signature cannot be
replayed as a valid signature in any other protocol that signs raw
DAG-CBOR content, and vice versa. Signatures computed over raw
coreBytes without the domain tag are invalid and MUST
be rejected.
These signatures are the authoritative root of trust for did:me and determine:
- whether an update is valid
- whether rollback, replay, or fork attempts are rejected
- whether the currentCore CID matches its signed content
Core signatures appear in:
"attestations": [
{
"alg": "ML-DSA-87",
"vm": "#mldsa87-root",
"sig": "<base64url(signature-over-domain-separated-core-signing-input)>"
}
]
Multiple signatures MAY be present (e.g., Ed25519 and ML-DSA-87)
to support multi-suite cryptography, and
updatePolicy.threshold (Section 5.4) determines how
many of them are required. For attestations.sig,
implementations MUST use base64url-encoded signature bytes. Every
attestation signature MUST be computed with the domain-separated
signing input defined above. The alg value of each
attestation MUST equal the algorithm value of the
controllerKeys entry referenced by vm —
the key’s declared key-type identifier (e.g.,
"Ed25519", "ML-DSA-87",
"P-256"), not a JOSE signature-algorithm name. did:me
attestations are domain-separated and are not JWS objects; the JOSE
alg register (EdDSA, ES256,
…) is used only inside genuine JWS structures such as the
DataIntegrity proof of Section 6.
7.2 Additional Signatures
A DID Document MAY include an optional W3C DataIntegrityProof using ES256 (P-256). This proof is not authoritative for update validation. See Section 6 for the full definition of the DataIntegrityProof and the es256-jws-cid-2025 cryptosuite.
8. Operations
8.1 Create
A new did:me identifier starts with the following core state:
sequence = 1
prev absent
nonce present (16 bytes)
Creation steps:
- Generate the 16-byte
nonce(randomly or seed-derived, per Section 2.5) - Construct the genesis
controllerKeysandupdatePolicy - Derive the identifier from the GenesisBinding per Section 2.5
- Construct the initial core object, including
id,nonce, and the material from step 2 - Encode the core using canonical DAG-CBOR (Section 5.6)
- Compute currentCore as CIDv1 (dag-cbor, sha2-256)
- Sign the core bytes (with domain separation, Section 7.1) using
enough allowed verification methods to satisfy
updatePolicy.threshold - Publish the resulting DID Document to an optional directory endpoint, or retain it in a wallet, device, or agent for local resolution
8.2 Update
To update a DID, the update processor MUST enforce all of the following:
new.id == old.id
new.sequence == old.sequence + 1
new.prev == old.currentCore
at least old.updatePolicy.threshold (default 1) attestations over the
domain-separated core signing input from Section 7.1 verify,
each under a DISTINCT verification method listed in
old.updatePolicy.allowedVerificationMethods, using key material from old.controllerKeys
If any check fails, the update MUST be rejected. The new
updatePolicy becomes authoritative only after the
update is accepted.
A successful update produces:
- a new core snapshot
- a new currentCore CID
- an appended entry in keyHistory
- an updated DID Document projection
8.2.1 Threshold and Multiple Controllers
A did:me DID MAY list one or more controllers.
An update is valid if and only if at least threshold
attestations (where threshold is the previously active
updatePolicy.threshold, defaulting to 1 when absent)
each verify under a verification method that:
- is listed in the previously active
updatePolicy.allowedVerificationMethods, - belongs to at least one currently active controller, and
- is distinct from the verification methods satisfying the other counted attestations (multiple attestations by the same verification method count once).
When threshold is 1 (or absent), any single such
verification method is sufficient. Deployments that require hybrid
AND-control — for example, a post-quantum signature AND a classical
signature on every update — set threshold: 2 with both
methods listed in allowedVerificationMethods, which
makes the dual-signature requirement enforceable at the method layer
rather than by application policy. Additional attestations beyond
the threshold MAY be included as extra evidence.
8.3 Deactivate
Deactivation is performed by publishing a terminal core: an ordinary core update (Section 8.2) whose content is emptied. A terminal core MUST have exactly the following shape:
id— unchangedsequence— previous sequence + 1prev— the previouscurrentCoreCID (nonceabsent, as for any non-genesis core)controller— unchanged from the previous corecontrollerKeys,authenticationKeys,assertionKeys,keyAgreementKeys,services— empty arrays (present in the CBOR map per Section 5.2)updatePolicy—{ allowedVerificationMethods: [] }with thethresholdkey absent
The empty allowedVerificationMethods is what makes
the core terminal: no verification method can ever satisfy the
update rule again, so no subsequent core can be valid. Processors
MUST treat any core whose
updatePolicy.allowedVerificationMethods is empty as a
terminal deactivation record and MUST reject any core claiming a
higher sequence for that DID. The internal-reference rules of
Sections 5.3 and 5.4 (references resolving into
controllerKeys, non-empty policy) apply to active
cores; the terminal core is the single defined exception, and a core
that empties controllerKeys or
allowedVerificationMethods in any other configuration
(e.g., empty keys with a non-empty policy) is invalid.
The terminal core MUST carry attestations satisfying the
previously active updatePolicy
(including its threshold), signed with key material
from the previous core’s controllerKeys, exactly as for
any update (Section 8.2).
After deactivation:
- Directory resolution returns
410 Gonefor the DID, and resolvers MUST report"deactivated": truein the DID Document metadata of the resolution result. Deactivation MUST NOT be reported as404 Not Found: absence and deactivation are distinct states (see Sections 3.2 and 11.6). - A stored deactivation record MAY still be published in a separate archival namespace for audit or compliance, but it MUST NOT participate as the active DID Document.
Deactivation records are terminal records and are not valid active did:me v1 default-profile DID Documents.
9. DID Document Projection
The DID Document is a JSON projection derived from the signed core. Validation MUST be performed against the core and its signatures, not the DID Document.
9.1 Field Mapping from Core
The following DID Document fields MUST be derived directly from the core:
id→ core.idcontroller→ core.controllersequence→ core.sequenceprev→ core.prevnonce→ core.nonce (genesis state only; omitted whensequence > 1)currentCore→ CID of the canonical DAG-CBOR core snapshotcoreCbor→ base64url-encoded canonical DAG-CBOR bytes of the current core snapshotverificationMethod→ derived from core.controllerKeysauthentication→ derived from core.authenticationKeysassertionMethod→ derived from core.assertionKeysupdatePolicy→ core.updatePolicycapabilityInvocation→ derived from core.updatePolicy.allowedVerificationMethods (the update-authorized root control keys)keyAgreement→ derived from core.keyAgreementKeysservice→ derived from core.services. If the core has no services, the service property MUST be omitted from the DID Document.keyHistory→ ordered list of prior core CIDs, reconstructed by followingprevlinks through the chain of core snapshots
These fields MUST reflect the core (or, for
keyHistory, the verified core chain) exactly, and MUST
NOT introduce information not present in the signed core snapshots.
Note that the projected keyHistory array is itself
unsigned convenience metadata: validators reconstruct and verify
history by walking the prev chain of signed cores, not
by trusting the projected array. The consistency checks in Section
15.1 (sequence == keyHistory.count + 1,
prev == last keyHistory entry) bind the projection’s
shape to the core, but the array’s earlier entries are only proven
by chain verification (Section 3.4).
9.2 Method-Specific Extensions (Non-Core Metadata)
The DID Document MAY include information that is not itself part of the core object:
attestations— authoritative signatures over the core objectproof— optional, non-authoritative Data Integrity Proof (P-256) over currentCoredomainVerification— optional, non-authoritative DNS/HTTP domain-binding- any other optional terms defined in the JSON-LD context at: https://did-me.org/ns/did-me/v1
Generic DID processors that do not recognize optional extension
metadata MUST ignore that metadata, per DID Core rules. did:me
processors MUST evaluate attestations when validating a
did:me DID Document. When projecting from core to DID Document JSON,
prev MUST be omitted when the core has no
prev field (genesis state) and MUST be present as a
non-null CID value when sequence > 1. Conversely,
nonce MUST be present (base64url-encoded) only for
genesis state. attestations are not part of the
canonical core object, but they are authoritative evidence for
validating the core snapshot. proof remains optional
metadata in v1; however, profiles that require a JWS/P-256 proof
over the compact currentCore commitment SHOULD include
it.
9.3 Domain Verification Objects
Each optional domainVerification entry MUST include:
- type (string) — the verification object type (“DnsTxtVerification” or “HttpsWellKnownVerification”)
- domain (string)
- method (string)
- method-specific verification data, such as binding, proofUrl, dns, or wellknown (depending on method)
Resolvers MUST accept at least one valid domain-verification method. Support for both is RECOMMENDED.
9.4 Requirements
- Reconstructing DID state MUST be possible using only the core snapshot and its signatures.
- The DID Document MUST NOT override or conflict with the contents of the signed core.
- Optional fields MUST NOT influence update authorization or core validity.
- The resolver MUST treat any mismatch between the core and projection as an error.
- If
coreCboris present, decoding it MUST produce canonical DAG-CBOR bytes whose CID equalscurrentCore. - In the did:me v1 default profile (Section 15),
coreCboris required to enable self-contained attestation verification.
10. JSON-LD Context
did:me JSON-LD context provides optional metadata
extensions. Implementations MUST ignore any unrecognized terms,
consistent with DID Core processing rules.
The did:me JSON-LD context is published at https://did-me.org/ns/did-me/v1.
This context defines:
- method-specific terms (
sequence,prev,nonce,currentCore,coreCbor,keyHistory,algorithm) - metadata terms (
updatePolicy,attestationsand its membersalg,vm,sig,proof,domainVerification) - service and verification types
(
DidMeDirectoryService,MessagingService,PaymentService,OpenID4VCI,OpenID4VP,EudiCredentialService,DnsTxtVerification,HttpsWellKnownVerification) - optional device- or persona-related extensions
(
hardwareBound,biometricProtected,userVerificationMethod,deviceModel) - optional EUDI trust-framework metadata
(
eudiLevelOfAssurance,eudiSchemaVersion), reserved for application profiles that need to annotate a DID Document or service with an eIDAS level of assurance or EUDI schema version - reserved extension terms (
format,attestation) held for future profile use; producers SHOULD NOT emit them in v1 documents
All terms defined in the context:
- MAY appear in DID Documents
- MUST NOT influence the authoritative core state unless explicitly part of the core schema
- MUST be ignored by processors that do not understand them, per DID Core rules
The JSON-LD context MUST be included in all did:me DID Documents
to guarantee consistent interpretation of method-specific terms. The
first three @context entries MUST be the canonical
did:me context tuple, in this exact order: 1.
https://www.w3.org/ns/did/v1 2.
https://w3id.org/security/multikey/v1 3.
https://did-me.org/ns/did-me/v1 Additional contexts MAY
follow and MUST NOT redefine terms from the canonical tuple.
Conformance checkers and validators SHOULD reject DID Documents
whose additional contexts redefine terms from the canonical
tuple.
10.1 alsoKnownAs (Optional Correlation Identifiers)
alsoKnownAs is an optional DID Core property that
MAY appear in a did:me DID Document. It provides human-readable or
system-assigned alternate identifiers, such as short profile URLs,
usernames, handles, or legacy identifiers. These values:
- do not participate in update authorization
- do not affect core validity
- do not alter the canonical core snapshot
- MAY be ignored by resolvers or relying parties
"alsoKnownAs": [
"https://domain/<short-id>"
]
alsoKnownAs is strictly non-authoritative metadata and serves only as an optional correlation convenience.
10.2 Hardware Bound and Biometric Protected
hardwareBound and biometricProtected
are optional metadata fields that describe protection
characteristics of the controller’s authentication environment.
These fields are not authoritative, do not affect core state, and
MAY be ignored by relying parties.
10.3 User Verification Method
userVerificationMethod is optional metadata
describing how a user authenticates locally (face, fingerprint, pin,
passcode, password, iris, voice, pattern, none). This field is
non-authoritative and MUST NOT influence the validity of a core
snapshot.
10.4 Device and Model
deviceModel is optional metadata identifying the
user’s device model or hardware platform. It is purely informational
and does not affect DID state.
11. Security Considerations
This section addresses the attack classes identified in RFC 3552 (eavesdropping, replay, message insertion, deletion, modification, and denial of service) as they apply to did:me, along with method-specific risks.
11.1 Chain Integrity and Update Authorization
- Rollback protection: enforced through monotonic sequence values
- Fork prevention: enforced through prev linking the previous core snapshot
- Tamper-evidence: achieved via CIDv1 (SHA-256, DAG-CBOR) and core signatures
- Authorized updates: restricted to verification
methods listed in updatePolicy, with at least
thresholddistinct valid attestations required (Section 8.2.1) - Canonical encoding: core signatures MUST cover the canonical DAG-CBOR bytes with the domain-separation tag (Section 7.1)
- Projection safety: DID Documents are non-authoritative projections; validators must rely on core + signatures
- Key rotation: does not change the DID; state changes only via new core snapshots
- Backup practices: operators SHOULD maintain redundant storage of core snapshots and DID Documents; loss of all update-authorized private keys permanently freezes the DID state
11.2 Identifier–Genesis Binding
The identifier is a truncated SHA-256 commitment to the genesis control material (Section 2.5), so a party who does not control the original genesis material cannot construct a competing, validly bound chain for an existing did:me identifier. Verifiers obtain this guarantee only at the chain-verified assurance level (Section 3.4); attestation-only verification of a non-genesis document proves internal consistency but not the identifier binding, and verifiers SHOULD perform chain verification on first encounter with a DID.
The commitment covers nonce,
controllerKeys, and updatePolicy only.
Genesis cores that differ solely in non-committed fields
(controller, services, relationship
arrays) share the same identifier; because such variants can only be
signed by the holder of the committed keys, this is
self-equivocation by the controller rather than a takeover vector.
Resolvers SHOULD pin the first-seen genesis core CID per DID and
treat any conflicting genesis core — whether it differs in committed
or non-committed fields — as a fork to be rejected.
Truncating SHA-256 to 128 bits preserves ~2^128 second-preimage resistance against attacks on a specific existing DID. Collision resistance of the truncated hash is ~2^64, but a collision requires the attacker to control both colliding genesis states; it therefore allows an attacker at most to equivocate between two genesis states for a DID they themselves created, which the fork-rejection rules already treat as invalid. It does not enable takeover of any honestly generated DID.
11.3 Freshness and Rollback by Omission
A directory, cache, or attacker replaying old documents can serve a stale-but-validly-signed core — for example, one listing a key that a later rotation retired after compromise. The sequence and prev rules only protect resolvers that have seen the newer state. Resolvers SHOULD cache the highest verified (sequence, currentCore) pair per DID and treat any regression as an error; relying parties with high assurance requirements SHOULD cross-check more than one directory or obtain the latest core directly from the controller (e.g., during a live protocol exchange). Deployments should treat freshness as a residual risk proportional to how long they are willing to accept a cached state.
11.4 Threshold Policy and Post-Quantum Weakest-Link
Update authorization with threshold: 1 is
weakest-link: compromise of any single verification method listed in
allowedVerificationMethods is sufficient to take over
the DID. In particular, listing a classical method (e.g., Ed25519)
alongside ML-DSA-87 at threshold 1 reduces the control plane’s
post-quantum security to the classical method. Deployments requiring
post-quantum control security SHOULD either list only post-quantum
methods in allowedVerificationMethods, or list hybrid
methods with a threshold of 2 or more so that both a
post-quantum and a classical signature are required (Section 5.4).
The default profile (Section 15) requires #mldsa87-root
to be present; it deliberately does not forbid additional methods,
so profile users make this trade-off explicitly.
11.5 Domain Separation and Cross-Protocol Replay
All core attestations are domain-separated with the tag
did:me:v1:core (Section 7.1), and the identifier
derivation is domain-separated with did:me:v1:genesis
(Section 2.5). This prevents a signature or hash computed for
another protocol over the same bytes from being replayed into
did:me, and vice versa. Future did:me versions or profiles MUST use
distinct tags.
11.6 Directory Trust, Deactivation, and Denial of Service
Directories are untrusted conveniences: all authority derives
from the signed core chain. Directory connections MUST use
authenticated transport (HTTPS) to prevent in-path modification, but
transport security does not make a directory’s content
authoritative. Two directory behaviors deserve attention:
withholding (serving stale state, Section 11.3) and
deactivation stripping — continuing to serve the
pre-deactivation document after the controller has deactivated. The
distinct 410 Gone deactivation state (Sections 3.2,
8.3) makes honest directories unambiguous, but a malicious directory
can still misreport; relying parties handling high-value
interactions SHOULD confirm liveness with the controller or multiple
directories. Directories are also a denial-of-service surface: their
unavailability affects discoverability but never DID validity, and
controllers SHOULD retain locally resolvable signed cores.
Directories SHOULD additionally publish an append-only, third-party-auditable log of the snapshots they serve (a transparency log). First-seen pinning (Section 11.2) then becomes globally auditable: equivocation between what a directory served to different resolvers is detectable by anyone comparing log inclusion proofs, rather than only by the parties who happened to receive conflicting snapshots. This matters most for high-value key material discovered through directories, such as messaging pre-keys (Section 5.11).
11.7 Key Compromise and Recovery
did:me v1 has no recovery mechanism separate from the update policy itself: if an attacker obtains keys satisfying the active update threshold, they can rotate the update policy and permanently seize the DID; if the controller loses all update-authorized keys, the DID is permanently frozen. Thresholds (Section 5.4) mitigate single-key compromise. Controllers SHOULD protect update-authorized keys with hardware-backed storage where available. A future profile may add pre-rotation commitments (committing to the hash of the next key set inside the current core) to further constrain post-compromise takeover.
Recovery patterns (non-normative). The
update-policy machinery already supports device-loss recovery;
deployments should choose a tier deliberately at DID creation, since
the genesis updatePolicy is part of the identifier
commitment:
- Seed-derived recovery. Because the
nonceMAY be KDF-derived (Section 2.5) and ML-DSA, ML-KEM, Ed25519, and X25519 key generation are deterministic from a seed (FIPS 204/203), a wallet can derive its entire genesis state from a single recovery seed. The seed phrase alone then regenerates the identifier, all keys, and full update capability after total device loss. - Backup key. Listing an additional verification
method in
controllerKeysandallowedVerificationMethodsat creation — held offline or on a second device — allows a single update signed by the backup key to rotate to replacement device keys. This protects against loss but not compromise: at threshold 1, an attacker holding any allowed key can race the controller. - Multi-key threshold (e.g., 2-of-3).
Distributing three allowed verification methods across device,
offline backup, and a second device or custodian, with
threshold: 2, protects against both the loss and the compromise of any single key: losing one key does not freeze the DID, and stealing one key does not permit takeover.
11.8 Cryptographic Agility
- Multi-suite coexistence: classical (Ed25519, ES256) and post-quantum (ML-DSA-87, ML-KEM-768, ML-KEM-1024) suites, referencing FIPS 204 and FIPS 203, allow deployments to phase suites in and out via key rotation without changing identifiers
- ECDSA S-Value Forms: ES256 signatures may use either low-S or high-S encoding. Both forms are secure and mathematically equivalent. Verifiers MUST accept both and MAY normalize signatures to low-S form internally for canonicality; signers SHOULD emit low-S (Section 6.4).
11.9 Messaging Pre-Key Forward Secrecy and Replay
Publishing messaging pre-keys in the DID Document (Section 5.11) is a signed-pre-key-only asynchronous handshake model: there are no server-consumed one-time pre-keys. The security consequences are bounded and deployments should understand them:
- Initial-message forward secrecy window. If a messaging pre-key private key is compromised, initial session messages recorded during that pre-key’s publication lifetime are exposed, up to the point where each session’s ratchet introduced fresh key material. The exposure window equals the rotation interval plus any retention grace window, which is why Section 5.11 recommends fixed-cadence rotation and prompt destruction of superseded keys.
- Post-quantum ratcheting. The statement that messages after the first ratchet step are unaffected holds against classical adversaries. Against a future quantum adversary it holds only if the session protocol’s own ratchet mixes post-quantum key material into its ongoing state: a classical-only DH ratchet leaves all recorded session traffic — not just initial messages — exposed to later quantum decryption. Messaging protocols consuming did:me pre-keys SHOULD therefore employ hybrid ratcheting, periodically mixing ML-KEM-derived secrets into the ratchet state, extending the hybrid requirement that Section 5.11 imposes on session establishment through the lifetime of the session.
- Initiation replay. Without one-time pre-keys, an initial handshake message can be replayed to the recipient, who would derive the same session secret. Messaging protocols consuming did:me pre-keys MUST mitigate initiation replay, for example by caching recently seen initiator ephemeral keys or by withholding session payload until a round trip has introduced fresh recipient key material.
- Key substitution resistance. Because pre-keys are carried in the fork-protected, first-seen-pinned core chain (Sections 11.2 and 11.3) rather than handed out by a delivery server, all senders observe the same pre-key material. Per-sender key substitution by infrastructure is not available as an attack, and equivocation attempts are subject to the same fork-rejection rules as any other conflicting snapshot.
- Key separation. Messaging pre-keys are key-agreement keys and are never update-authorized. Compromise of a messaging pre-key therefore does not enable DID takeover (Section 11.7); it affects message confidentiality only, within the window above.
- Post-compromise security layering. Session-layer self-healing after state compromise is the ratchet’s job. Pre-key rotation provides the identity-layer analogue: a fresh pre-key published in a signed core update re-anchors future session establishment to key material the earlier compromise never touched. The two layers are complementary; neither substitutes for the other.
- Messaging profile separation. Direct messaging and MLS messaging can use the same DID-published key-agreement material, but their live protocol state is separate from the DID Document. Ratchet state, MLS epochs, group membership, spaces, channels, message counters, and forward keys are messaging-layer state and MUST NOT be published as did:me core state.
12. Privacy Considerations
did:me DIDs can represent individuals, pseudonymous personas, organizations, groups, and device- or wallet-mediated identities. Implementations and relying parties MUST treat DID Documents and directory records as potentially privacy-sensitive, even when the values are public or self-asserted.
12.1 Data Minimization
Controllers SHOULD publish only the fields required for
resolution, verification, and the intended relying-party
interaction. Optional fields such as alsoKnownAs,
domainVerification, hardwareBound,
biometricProtected,
userVerificationMethod, deviceModel,
service endpoints, payment addresses, and other metadata can
increase linkability or reveal operational details. These fields
MUST NOT be required by generic resolvers for DID validity unless a
separate application profile explicitly requires them.
Generic did:me DID Documents and core snapshots MUST NOT contain raw biometric templates, private keys, recovery secrets, seed material, or unnecessary personal data. Application profiles that require personal data in DID-related records MUST define separate consent, disclosure, retention, and minimization requirements.
12.2 Correlation and Persona Separation
Stable identifiers and alsoKnownAs values can enable
correlation across contexts. Controllers SHOULD use separate did:me
identifiers for separate personas, roles, devices, wallets, or
unlinkable interaction contexts. Relying parties MUST NOT assume
that two did:me identifiers refer to the same subject unless that
relationship is explicitly disclosed and independently verified.
Directory operators and resolvers SHOULD avoid creating
cross-context indexes that correlate DID identifiers,
alsoKnownAs values, service endpoints, device metadata,
payment addresses, or domain bindings unless the controller has
intentionally published that relationship for the applicable
context.
Controllers and implementers SHOULD avoid reusing verification methods, infrastructure accounts, service endpoints, payment addresses, or directory operator accounts across personas or across identity and infrastructure layers unless correlation across those contexts is intentional. When a deployment uses public infrastructure or ledger-facing accounts, those accounts SHOULD be separated from DID controller keys and identity payload signing keys.
12.3 Biometric, Device, and Local Authentication Metadata
Fields such as biometricProtected,
hardwareBound, userVerificationMethod, and
deviceModel describe authentication environment
characteristics. They MUST be treated as non-authoritative metadata,
not as proof that a biometric, secure element, hardware model, or
local unlock method was actually used. Implementations SHOULD avoid
publishing precise device model, biometric modality, or local
authentication details unless needed for a specific trust framework,
because such details can aid fingerprinting or targeted attacks.
12.4 Directory Publication and Retention
Publishing a DID Document to an optional directory can disclose service endpoints, update timing, key history length, domain relationships, and other metadata. Directory publication SHOULD be opt-in by the controller. Directory operators SHOULD provide deletion or de-listing mechanisms for discoverability records where compatible with the deployment model, SHOULD minimize access logs, and SHOULD avoid retaining resolver query data longer than necessary for abuse prevention and operational integrity.
Resolvers SHOULD support local or wallet-provided resolution from signed core chains so that controllers are not forced to disclose DID usage to a public directory for every verification event.
12.5 Resolver and Presentation Tracking
DID resolution, service endpoint retrieval, credential status checks, revocation checks, or other DID-adjacent resource fetches can reveal when and where a DID is being used. Implementations SHOULD avoid resolution flows that require a controller, issuer, directory operator, or service provider to be contacted during every presentation or verification event when local verification from signed core data is sufficient.
Credential and presentation protocols that use did:me identifiers SHOULD prefer privacy-preserving status, revocation, and presentation mechanisms that do not allow issuers or infrastructure operators to track relying-party interactions. Resolvers MAY use privacy-enhancing transport or trusted resolver services where appropriate, but relying parties SHOULD NOT treat network privacy controls as a substitute for minimizing published metadata and avoiding unnecessary callbacks.
12.6 Key History and Update Metadata
The sequence, prev,
currentCore, coreCbor, and
keyHistory fields support integrity, replay protection,
and auditability, but they can also reveal update cadence and
operational events. Implementations SHOULD avoid embedding unrelated
personal data in signed core snapshots and SHOULD avoid publishing
historical snapshots beyond what is necessary for verifiable state
reconstruction and the applicable retention policy. Messaging
pre-key rotations (Section 5.11) are ordinary core updates and are
therefore publicly visible; rotating on a fixed cadence avoids
turning rekey timing into an observable operational signal.
12.7 Service Endpoints and Payment Addresses
Service endpoints, wallet addresses, and domain bindings can identify providers, infrastructure, organizations, or payment activity. Controllers SHOULD use pairwise or context-specific service endpoints when unlinkability is required. Payment address services SHOULD be omitted unless the DID is intentionally used for payment discovery, because public wallet addresses can expose transaction graph information outside the DID system.
Messaging delivery endpoints (Section 5.11) reveal a controller’s relay choice to every resolver. Deployments that need sender or relationship unlinkability SHOULD use shared or oblivious relay endpoints and SHOULD use delivery protocols that authenticate the sender to the recipient inside the encrypted payload rather than to the relay (sealed-sender designs), so that the relay learns at most the recipient of a deposit, not the social-graph edge between two identities.
12.8 Privacy-Preserving Verification
The optional Data Integrity proof over currentCore
is intended to support compact commitments and
zero-knowledge-compatible workflows. Verifiers SHOULD prefer
presentation protocols that disclose the minimum necessary claims
and SHOULD NOT require publication of optional metadata merely to
satisfy DID resolution. Application profiles SHOULD document any
additional metadata they require and the privacy impact of that
requirement.
13. Decentralization and Federation
did:me identifiers are fundamentally self-hosted and do not rely on any central registry. Optional public directories may mirror published DID Documents for discovery. The method’s structure supports decentralized and federated deployments without breaking existing identifiers.
The method supports:
- Multi-directory federation – multiple independent operators may host equivalent projections of the same DID state for discovery.
- Verifiable replay – any resolver can reconstruct canonical DID state by validating the chain of signed core snapshots.
- Independent resolution – resolvers do not need to trust any directory, host, or service; all validation derives from the signed core chain.
This is enabled by:
sequence– enforces strict update ordering
prev– links each core snapshot to the previous CID
currentCore– cryptographic addressing of core snapshots
- signed canonical cores – guarantee tamper-evident integrity
These features allow did:me to evolve toward decentralized, distributed, or mirrored resolution models in the future.
14. Illustrative did:me JSON member order (non-normative)
JSON member order is non-normative. Producers MAY emit members in any order, and processors MUST NOT rely on JSON key order for validity.
@context
id
controller
alsoKnownAs
sequence
prev
nonce
hardwareBound
biometricProtected
userVerificationMethod
deviceModel
coreCbor
currentCore
keyHistory
verificationMethod
authentication
assertionMethod
capabilityInvocation
keyAgreement
service(s)
updatePolicy
attestations
proof
15. Default Profile Requirements Matrix (did:me v1)
This section is normative for the did:me v1 default interoperability profile. The base method model is defined in Sections 5 through 9; this profile adds stricter required key sets and relationship requirements for public did:me v1 deployments.
This table defines for every DID Document field:
- Required?: MUST appear for a valid did:me v1
default-profile DID Document
- Nullable?: Whether the field may be
null
- Emit When Empty?: Whether to include the JSON
key when the value is empty
- Notes: Method-specific behavior
15.1 DID Document Field Matrix
| Field | Required | Nullable | Emit When Empty | Notes |
|---|---|---|---|---|
@context |
Yes | No | N/A | The first 3 entries MUST be, in order:
https://www.w3.org/ns/did/v1,
https://w3id.org/security/multikey/v1,
https://did-me.org/ns/did-me/v1. Additional contexts
MAY follow and MUST NOT redefine terms from the canonical
tuple. |
| id | Yes | No | N/A | MUST be a valid did:me: identifier. |
| controller | Yes | No | N/A | MUST be either: (a) a did:me: string, or (b) a
non-empty array of did:me: strings. |
| alsoKnownAs | No | No | Omit if empty | Optional. MUST be an array when present. |
| sequence | Yes | No | N/A | MUST equal keyHistory.count + 1. |
| prev | Conditional | No | Omit when sequence=1 | MUST be omitted when sequence=1; otherwise MUST equal last keyHistory entry. |
| nonce | Conditional | No | Omit when sequence>1 | MUST be present (base64url of the 16-byte genesis nonce) when sequence=1; MUST be omitted otherwise. |
| hardwareBound | No | No | Omit if null/false | Optional metadata. False treated as absent. |
| biometricProtected | No | No | Omit if null/false | Optional metadata. False treated as absent. |
| userVerificationMethod | No | No | Omit if null | Optional (“face”, “pin”, etc.). |
| deviceModel | No | No | Omit if null/empty | Optional metadata. |
| coreCbor | Yes | No | N/A | Base64url-encoded canonical DAG-CBOR bytes of the current core
snapshot; decoded bytes MUST hash to currentCore. |
| currentCore | Yes | No | N/A | MUST be CIDv1 of the core snapshot. |
| keyHistory | Yes | No | Emit empty array | MUST contain only past CIDs; MUST NOT include currentCore. |
| verificationMethod | Yes | No | N/A | MUST contain all required key types. |
| authentication | Yes | No | N/A | MUST contain at least #ed25519 and
#mldsa87-auth. |
| assertionMethod | Yes | No | N/A | MUST include #p256. |
| capabilityInvocation | Yes | No | N/A | MUST include #mldsa87-root. |
| keyAgreement | Yes | No | N/A | MUST include #x25519 and at least one of
#mlkem768 or #mlkem1024. |
| service | No | No | Omit if empty | Include only when one or more services exist. |
| updatePolicy | Yes | No | N/A | MUST include allowedVerificationMethods containing
#mldsa87-root. MAY include threshold
(Section 5.4); see Section 11.4 before adding classical
methods. |
| attestations | Yes | No | N/A | MUST have ≥1 ML-DSA-87 core signature, and at least
threshold valid signatures overall. |
| proof | No | No | Omit if absent | Optional in v1; strongly recommended for profiles requiring a
JWS/P-256 proof over currentCore. |
| domainVerification | No | No | Omit if empty | Optional DNS/HTTP domain-bindings. |
15.2 Null vs Omission Rules Summary
NEVER emit explicit null in the JSON DID
Document.
- Optional fields MUST be omitted entirely when not present.
- Arrays MUST either contain values or be omitted (except
keyHistory, which MUST be an empty array when no entries exist). - Optional booleans (
hardwareBound,biometricProtected) SHOULD be omitted when false unless explicitly set.
15.3 Required Arrays (non-null)
These MUST always be present in the JSON and MUST NOT be null:
- verificationMethod
- authentication
- assertionMethod
- capabilityInvocation
- keyAgreement
- attestations
- keyHistory (may be empty but MUST be present)
15.4 Optional Arrays
These MAY be omitted entirely:
- alsoKnownAs
- service
- domainVerification
{
"domainVerification": [
{
"type": "DnsTxtVerification",
"method": "dns",
"domain": "example.com",
"dns": {
"recordName": "_did",
"txtValue": "did=me:123..."
}
}
]
}
or
{
"domainVerification": [
{
"type": "HttpsWellKnownVerification",
"method": "wellknown",
"domain": "example.com",
"wellknown": {
"uri": "/.well-known/did-configuration.json",
"content": "eyAidHlwZSI6ICJkaWQtY29uZmlnIiB9"
}
}
]
}
domainVerification entries are self-asserted claims added by the DID controller. Resolvers and relying parties MUST NOT assume these claims are accurate. Independent verification MUST be performed by the verifier.
16. DID Document Relationship Definitions
verificationMethod
Definition:
A list of all public keys the DID controls.
Meaning:
These keys exist and can be referenced by other DID Document
sections.
This list does not define how the keys are used; roles
appear below.
Used for:
- Declaring available keys
- Referencing keys in authentication, assertionMethod, etc.
- Interoperability with DID Core processors
authentication
Definition:
Keys used to prove control of the DID in interactive protocols.
Used for:
- Logging in
- Pairwise authentication
- Messaging identity proofs
- Presentations that require DID control verification
Key types:
- Ed25519
- ML-DSA-87
- P-256
(Never X25519 or ML-KEM; they cannot sign.)
assertionMethod
Definition:
Keys used to sign statements made by the DID, including
VCs, public claims, and proofs over compact commitments.
Used for:
- Verifiable Credential issuance
- Identity claims
- Public profile statements
- P-256 signatures for hardware-backed and standards-compatible proof workflows
Key types:
- Ed25519
- ML-DSA-87
- P-256
(Never X25519 or ML-KEM.)
keyAgreement
Definition:
Keys used to derive shared secrets for encrypted channels.
Used for:
- Secure messaging
- Encrypted VC presentation
- Session establishment
- Hybrid encryption (X25519 + ML-KEM-768 + ML-KEM-1024)
Key types:
- X25519
- ML-KEM-768
- ML-KEM-1024
(Never Ed25519 or P-256; those are signing keys.)
This specification defines how key-agreement public keys are published, not how they are used: the concrete encryption protocol — e.g., HPKE, X-Wing for X25519 plus ML-KEM-768, another domain-separated hybrid X25519/ML-KEM combiner, direct messaging, or an MLS-based messaging profile — is defined by the application or messaging protocol that consumes these keys. Two parties MUST agree on that protocol out of band or via protocol negotiation; publishing both classical and post-quantum key-agreement keys is what enables hybrid schemes, but does not by itself define one.
Section 5.11 defines how a controller designates specific
key-agreement keys as messaging pre-keys, how senders discover them
through a MessagingService entry, and the rotation,
retention, and hybrid requirements that apply to that use.
capabilityInvocation
Definition:
Keys authorized to change or update DID state. These are the
root control keys.
Used for:
- DID Document updates
- Key rotation
- Recovery operations
- Deactivation
Key types:
- ML-DSA-87 (recommended primary root)
- Ed25519 (optional second root for hybrid AND-control)
(Never X25519 or ML-KEM. P-256 should only be used if Secure Enclave must sign updates.)
attestations
Definition:
Signatures over the canonical core snapshot (DAG-CBOR) that validate
DID state.
Used for hybrid AND-security.
Used for:
- Proving core snapshot authenticity
- True hybrid root control (Ed25519 AND ML-DSA-87)
- Long-term survivability across cryptographic eras
Key types:
- Ed25519
- ML-DSA-87
proof
Definition:
A non-authoritative P-256 Data Integrity Proof over the
compact currentCore commitment, for compatibility with
Apple Secp256r1 flows, DI verifiers, and proof systems that consume
CID commitments.
Used for:
- Proof integrations over
currentCore - Apple Secure Enclave-backed identities
- EUDI Wallet compatibility
- JWS-based verifiers
Key types:
- P-256 only
(Does not control DID updates.)
The following table is non-normative policy guidance. References
to dual or hybrid control (“AND”) describe deployments that list
both verification methods in
updatePolicy.allowedVerificationMethods with
threshold: 2, which makes the dual-signature
requirement enforceable at the method layer (Sections 5.4 and
8.2.1). Rows that omit required default-profile elements describe
specialized or private profiles and are not valid did:me v1
default-profile DID Documents unless the requirements in Section 15
are also satisfied.
| DID Type | Authentication | Assertion / Proofs | Verification Methods (VM Set) | Key Agreement | Capability Invocation (Root Control) | Attestations (Core Update) |
|---|---|---|---|---|---|---|
| Core Identity (private wallet, root identity) | Ed25519 + ML-DSA-87 + P-256 | ML-DSA-87 + Ed25519 + P-256 over compact commitments | Ed25519, ML-DSA-87, P-256, X25519, ML-KEM-768, ML-KEM-1024 | X25519 + ML-KEM-768 and/or ML-KEM-1024 | ML-DSA-87 AND Ed25519 (dual required) | Required dual (ML-DSA + Ed25519) |
| Public Profile (persona; receives selective-disclosure proofs) | Ed25519 + P-256 (+ optional ML-DSA-87) | Ed25519 + P-256 (+ optional ML-DSA-87) | Ed25519, ML-DSA-87, P-256, X25519, ML-KEM-768, ML-KEM-1024 | X25519 + ML-KEM-768 and/or ML-KEM-1024 | ML-DSA-87 only | Optional (P-256 DI proof recommended) |
| Public Profile + Issuer (VC issuer persona) | Ed25519 + ML-DSA-87 + P-256 | ML-DSA-87 (primary) + Ed25519 + P-256 | Ed25519, ML-DSA-87, P-256, X25519, ML-KEM-768, ML-KEM-1024 | X25519 + ML-KEM-768 and/or ML-KEM-1024 | ML-DSA-87 AND Ed25519 | Optional dual or PQ-only |
| Issuer DID (Harvard, banks, orgs) | Ed25519 + ML-DSA-87 + P-256 | ML-DSA-87 (primary) + Ed25519 + P-256 | Ed25519, ML-DSA-87, P-256 | Optional | ML-DSA-87 AND Ed25519 | Optional dual or PQ-only |
| Messaging DID (fast, throwaway, persona-tied) | Ed25519 (+ optional P-256) | None or P-256 when a hardware-backed JWS proof is required | Ed25519, P-256, X25519, ML-KEM-768, ML-KEM-1024 | X25519 + ML-KEM-768 and/or ML-KEM-1024 | Ed25519 | Single Ed25519 (minimum required signature) |
| Payment / Wallet DID (optional specialization) | secp256k1 + optional P-256 | Optional | secp256k1, P-256 | Rare | secp256k1 | Single secp256k1 (minimum required signature) |
17. Conformance Test Vectors (Informative)
This section provides implementation-oriented conformance vectors. It is informative and does not replace normative requirements elsewhere in this specification.
17.1 @context
Canonical Prefix
17.1.1 Valid
The first three @context entries match the canonical
tuple in order:
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/multikey/v1",
"https://did-me.org/ns/did-me/v1",
"https://example.org/extra-context"
]
Expected result: ACCEPT
17.1.2 Invalid
Wrong order in first three entries:
"@context": [
"https://w3id.org/security/multikey/v1",
"https://www.w3.org/ns/did/v1",
"https://did-me.org/ns/did-me/v1"
]
Expected result: REJECT
17.1.3 Invalid (Term Redefinition in Additional Context)
Additional context attempts to redefine a canonical term:
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/multikey/v1",
"https://did-me.org/ns/did-me/v1",
{
"currentCore": "https://example.org/redefinedCurrentCore"
}
]
Expected result: REJECT
17.2
coreCbor and currentCore Consistency
17.2.1 Valid
coreCbordecodes as canonical DAG-CBOR core bytes- CID(core bytes) equals
currentCore
Expected result: ACCEPT
17.2.2 Invalid
coreCbordecodes, but CID(core bytes) does not equalcurrentCore
Expected result: REJECT
17.3 sequence,
prev, and keyHistory
17.3.1 Valid Genesis
"sequence": 1,
"keyHistory": []
prev is omitted in the DID Document projection for
genesis.
Expected result: ACCEPT
17.3.2 Invalid Non-Genesis
"sequence": 3,
"keyHistory": ["cidA", "cidB"],
"prev": "cidA"
Expected result: REJECT (prev MUST
equal last keyHistory entry for
sequence > 1)
17.4 Core Attestation Validation
17.4.1 Valid
"attestations": [
{
"alg": "ML-DSA-87",
"vm": "#mldsa87-root",
"sig": "<valid-base64url-signature-over-domain-separated-core-signing-input>"
}
]
Expected result: ACCEPT when:
vmexists inverificationMethodalgmatches verification method algorithm- decoded
sigverifies over the domain-separated signing input for the decodedcoreCborbytes (Section 7.1) - at least
updatePolicy.threshold(default 1) attestations satisfy the above under distinct allowed verification methods
17.4.2 Invalid
"attestations": [
{
"alg": "ML-DSA-87",
"vm": "#mldsa87-root",
"sig": "!!!not-base64url!!!"
}
]
Expected result: REJECT
17.5 Data Integrity Proof Behavior
17.5.1 Valid (Supported Suite)
"proof": {
"type": "DataIntegrityProof",
"cryptosuite": "es256-jws-cid-2025",
"proofPurpose": "assertionMethod",
"verificationMethod": "#p256",
"created": "2026-01-01T00:00:00Z",
"jws": "<valid-compact-jws>"
}
Expected result:
- ACCEPT only if all cryptosuite checks pass,
including:
typeisDataIntegrityProofcryptosuiteises256-jws-cid-2025- the referenced
verificationMethodis listed inassertionMethod(Section 6.3) - protected JWS header is exactly
{"alg":"ES256"} - payload equals
currentCore - ES256 signature verifies with the referenced P-256 verification method
- proof remains non-authoritative for update validity
17.5.2 Unsupported Suite Handling
"proof": {
"type": "DataIntegrityProof",
"cryptosuite": "unsupported-suite",
"jws": "header.payload.signature"
}
Expected result:
- proof treated as unsupported
- proof MUST NOT be treated as valid
- core-based DID validity processing continues
17.6 Identifier–Genesis Binding
These values are computed from the conformance key vectors using
the algorithm in Section 2.5, with the fixed test nonce
a0a1a2a3a4a5a6a7a8a9aaabacadaeaf (hex). The full
GenesisBinding CBOR bytes and genesis core bytes are published with
this specification in didme-genesis.json at
https://did-me.org/spec/v1/vectors/ (also in the
vectors/ directory of the specification
repository).
17.6.1 Valid
nonce (hex): a0a1a2a3a4a5a6a7a8a9aaabacadaeaf
payload (hex): 7f9597f7db25f39a37b47b9715f15d2c
id: did:me:me1072e0a7myhee5da50wt3tu2a9sds44xv
currentCore (CID): bafyreiflaivklbly4ad6kdgmufu26vfnls2hvvusutlpqrsti2dy7cntdi
A genesis core carrying this nonce, controllerKeys, and updatePolicy, presented for the DID above:
Expected result: ACCEPT (recomputed identifier matches)
17.6.2 Invalid (Wrong Genesis)
The same genesis core presented for any other did:me identifier,
or a genesis core whose nonce,
controllerKeys, or updatePolicy differ in
any byte from the values committed by the identifier:
Expected result: REJECT (recomputed identifier does not match; the entire chain built on this genesis MUST be rejected)
17.6.3 Invalid (Case)
did:me:ME1APVVV5K5A7Y5QA8HSQZWRK9MMVN6NHYT
Expected result: REJECT (uppercase; only the lowercase Bech32 form is a valid did:me identifier)
17.7 Update Threshold
17.7.1 Valid
Previous core has
updatePolicy = { allowedVerificationMethods: ["#mldsa87-root", "#ed25519"], threshold: 2 }.
The new core snapshot carries one valid ML-DSA-87 attestation under
#mldsa87-root and one valid Ed25519 attestation under
#ed25519.
Expected result: ACCEPT
17.7.2 Invalid (Below Threshold)
Same previous policy; the new core snapshot carries only the ML-DSA-87 attestation.
Expected result: REJECT (1 < threshold 2)
17.7.3 Invalid (Duplicate Method)
Same previous policy; the new core snapshot carries two
attestations both verifying under #mldsa87-root.
Expected result: REJECT (attestations under the same verification method count once)
17.8 MessagingService Entries
17.8.1 Valid
A DID Document lists #x25519-msg (X25519) and
#mlkem768-msg (ML-KEM-768) under
keyAgreement, and includes:
{
"id": "#messaging",
"type": "MessagingService",
"serviceEndpoint": {
"uri": "https://relay.example.com/inbox/7f3a",
"preKeys": ["#x25519-msg", "#mlkem768-msg"]
}
}
Expected result: ACCEPT
17.8.2 Invalid (Pre-Key Not a keyAgreement Method)
The same service entry, but #x25519-msg appears only
under authentication and not under
keyAgreement.
Expected result: REJECT (Section 5.11: every
preKeys entry MUST reference a verification method in
the keyAgreement relationship)
17.8.3 Invalid (Classical-Only Pre-Key Set)
The same service entry, but preKeys lists only
#x25519-msg.
Expected result: REJECT for asynchronous session establishment (Section 5.11: a pre-key set MUST reference at least one X25519 key and at least one ML-KEM-768 or ML-KEM-1024 key)
Machine-readable versions of these cases (plus post-quantum-only
and dangling-reference variants) are published in the repository at
vectors/v1/messaging-service.json.
18. Intellectual Property and Licensing
This specification, the did:me JSON-LD context, and other non-software documents in the did:me specification repository are licensed under the W3C Software and Document License, except where otherwise indicated.
Sample implementations, tooling, and other software contributions in the did:me specification repository are licensed under the Apache License 2.0, except where otherwise indicated.
The did:me method name and related project marks are subject to the trademark notices published with the specification repository. Registration of the did:me DID method in the W3C DID Extensions registry is not a W3C endorsement of the method, its implementations, or any related marks.
19. References
19.1 Normative References
- [DID-CORE] Decentralized Identifiers (DIDs) v1.0. W3C Recommendation. https://www.w3.org/TR/did-core/
- [DID-RESOLUTION] Decentralized Identifier Resolution (DID Resolution). W3C. https://www.w3.org/TR/did-resolution/
- [RFC 2119] Key words for use in RFCs to Indicate Requirement Levels. https://www.rfc-editor.org/rfc/rfc2119
- [RFC 8174] Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words. https://www.rfc-editor.org/rfc/rfc8174
- [RFC 8949] Concise Binary Object Representation (CBOR). https://www.rfc-editor.org/rfc/rfc8949
- [DAG-CBOR] IPLD DAG-CBOR Specification. https://ipld.io/specs/codecs/dag-cbor/spec/
- [CID] Content Identifiers (CIDv1). https://github.com/multiformats/cid
- [MULTIBASE] Multibase. https://github.com/multiformats/multibase
- [MULTICODEC] Multicodec table. https://github.com/multiformats/multicodec
- [BIP-173] Bech32 address format. https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
- [RFC 7515] JSON Web Signature (JWS). https://www.rfc-editor.org/rfc/rfc7515
- [RFC 7518] JSON Web Algorithms (JWA). https://www.rfc-editor.org/rfc/rfc7518
- [FIPS 180-4] Secure Hash Standard (SHS). https://doi.org/10.6028/NIST.FIPS.180-4
- [FIPS 203] Module-Lattice-Based Key-Encapsulation Mechanism Standard (ML-KEM). https://doi.org/10.6028/NIST.FIPS.203
- [FIPS 204] Module-Lattice-Based Digital Signature Standard (ML-DSA). https://doi.org/10.6028/NIST.FIPS.204
- [MULTIKEY] Controlled Identifiers / Multikey. https://www.w3.org/TR/cid-1.0/
- [SEC1] SEC 1: Elliptic Curve Cryptography (compressed point encoding). https://www.secg.org/sec1-v2.pdf
19.2 Informative References
- [BIP-350] Bech32m format (referenced only to state that did:me does not use it). https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki
- [VC-DATA-INTEGRITY] Verifiable Credential Data Integrity 1.0. https://www.w3.org/TR/vc-data-integrity/
- [OID4VCI] OpenID for Verifiable Credential Issuance. https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html
- [OID4VP] OpenID for Verifiable Presentations. https://openid.net/specs/openid-4-verifiable-presentations-1_0.html
- [EUDI-ARF] European Digital Identity Wallet Architecture and Reference Framework. https://eu-digital-identity-wallet.github.io/eudi-doc-architecture-and-reference-framework/
- [RFC 3552] Guidelines for Writing RFC Text on Security Considerations. https://www.rfc-editor.org/rfc/rfc3552
- [RFC 9180] Hybrid Public Key Encryption. https://www.rfc-editor.org/rfc/rfc9180
- [RFC 9420] The Messaging Layer Security (MLS) Protocol. https://www.rfc-editor.org/rfc/rfc9420
- [XWING] X-Wing: general-purpose hybrid post-quantum KEM (X25519 and ML-KEM-768). https://datatracker.ietf.org/doc/draft-connolly-cfrg-xwing-kem/
Appendix A. Example DID Document (Non-Normative, Abridged)
The following genesis-state (sequence = 1) DID Document conforms
to the did:me v1 default profile (Section 15). It is an
abridged rendering: the long post-quantum key
values, coreCbor, and signature values are shortened
with … for readability. The identifier, CID, nonce, key
material, and attestations are real; every elided key, CBOR value,
and signature is available in complete, machine-readable form in the
conformance vectors at https://did-me.org/spec/v1/vectors/ (see
Section 17.6).
{
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/multikey/v1",
"https://did-me.org/ns/did-me/v1"
],
"id": "did:me:me1072e0a7myhee5da50wt3tu2a9sds44xv",
"controller": "did:me:me1072e0a7myhee5da50wt3tu2a9sds44xv",
"sequence": 1,
"nonce": "oKGio6SlpqeoqaqrrK2urw",
"coreCbor": "qmJpZHgqZGlkOm1lOm1lMTA3MmUwYTdteWhlZTVkYTUwd3QzdHUyYTlzZHM0NHh2ZW5vbmNlUKChoqOk…",
"currentCore": "bafyreiflaivklbly4ad6kdgmufu26vfnls2hvvusutlpqrsti2dy7cntdi",
"keyHistory": [],
"verificationMethod": [
{
"id": "#ed25519",
"type": "Multikey",
"controller": "did:me:me1072e0a7myhee5da50wt3tu2a9sds44xv",
"algorithm": "Ed25519",
"publicKeyMultibase": "z6Mkf68ponjAdBLZ1rAhxkpiNCqKdVY1…"
},
{
"id": "#mldsa87-auth",
"type": "Multikey",
"controller": "did:me:me1072e0a7myhee5da50wt3tu2a9sds44xv",
"algorithm": "ML-DSA-87",
"publicKeyMultibase": "z5fhCbCK3ai95oafzBwyf8vKDVhKqVXJ…"
},
{
"id": "#mldsa87-root",
"type": "Multikey",
"controller": "did:me:me1072e0a7myhee5da50wt3tu2a9sds44xv",
"algorithm": "ML-DSA-87",
"publicKeyMultibase": "z5fhRsyrZ4RFeXUyiJujSc6t64Y9q3ER…"
},
{
"id": "#mlkem1024",
"type": "Multikey",
"controller": "did:me:me1072e0a7myhee5da50wt3tu2a9sds44xv",
"algorithm": "ML-KEM-1024",
"publicKeyMultibase": "zmsZT6URCA4WuMsV1FJTSVGxEavnq5q3…"
},
{
"id": "#mlkem768",
"type": "Multikey",
"controller": "did:me:me1072e0a7myhee5da50wt3tu2a9sds44xv",
"algorithm": "ML-KEM-768",
"publicKeyMultibase": "z9LYfmneXVRdaGXEd8DJxofvuTok5kDV…"
},
{
"id": "#p256",
"type": "Multikey",
"controller": "did:me:me1072e0a7myhee5da50wt3tu2a9sds44xv",
"algorithm": "P-256",
"publicKeyMultibase": "zDnaepCUWe4u5bpxSunUZfScPkVXTWYV…"
},
{
"id": "#x25519",
"type": "Multikey",
"controller": "did:me:me1072e0a7myhee5da50wt3tu2a9sds44xv",
"algorithm": "X25519",
"publicKeyMultibase": "z6LSfnA88DsBL74TPv7Xate2wQE6rwmk…"
}
],
"authentication": ["#ed25519", "#mldsa87-auth", "#p256"],
"assertionMethod": ["#ed25519", "#p256", "#mldsa87-auth"],
"capabilityInvocation": ["#mldsa87-root", "#ed25519"],
"keyAgreement": ["#x25519", "#mlkem768", "#mlkem1024"],
"updatePolicy": {
"allowedVerificationMethods": ["#mldsa87-root", "#ed25519"],
"threshold": 2
},
"attestations": [
{
"alg": "Ed25519",
"vm": "#ed25519",
"sig": "V8ZpzyLzvIBa-QPneePA7CQMMKWrz2JaPkK3IwZ_7crmnTzxN0hEyE-BpJOHls6y…"
},
{
"alg": "ML-DSA-87",
"vm": "#mldsa87-root",
"sig": "z50-VAN4JkOqGVSQhwt6tff5yee9qskdJe4tjqlM_xPOY1GuA2bKz_CRKRVCte8r…"
}
]
}
Notes:
- The example uses distinct ML-DSA-87 test-vector keys for
#mldsa87-rootand#mldsa87-auth, matching the default-profile separation between update control and authentication. - The example uses the default-profile hybrid update policy:
"allowedVerificationMethods": ["#mldsa87-root", "#ed25519"], "threshold": 2, and therefore carries one valid attestation from each listed method. serviceis omitted because the genesis core declares no services (Section 15.1).- Because this document is genesis state, it is fully self-verifying, including the identifier binding (Sections 2.6 and 3.4).