Skip to content

DID Method Mechanics (key, jwk, web, webvh 1.0)

The did:key method derives the entire DID document deterministically from the public key encoded within the method-specific identifier.

did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK
│└─────────────────────────────────────────────┘
│ Base58BTC Encoded Payload
▼
Multibase Prefix 'z'
│
▼ (Base58BTC Decode)
┌────────────────────────┬───────────────────────────────────────────┐
│ Varint Multicodec │ Raw Public Key Bytes │
│ (e.g., 0xed = Ed25519) │ (32 bytes Ed25519 / 33 bytes compressed) │
└────────────────────────┴───────────────────────────────────────────┘

Decoding is executed via unsigned_varint::decode::u64 against the raw byte buffer obtained after stripping the leading base58btc 'z' prefix.

Codec Identifier Hex Codec Varint Wire Bytes Raw Key Length Cryptographic Curve / Type Key Purpose Mapping
MC_ED25519_PUB 0xed [0xed, 0x01] 32 bytes Edwards Ed25519 authentication, assertionMethod, capabilityInvocation, capabilityDelegation
MC_X25519_PUB 0xec [0xec, 0x01] 32 bytes Montgomery X25519 keyAgreement (ONLY)
MC_P256_PUB 0x1200 [0x80, 0x24] 33 bytes NIST P-256 (secp256r1) authentication, assertionMethod, capabilityInvocation, capabilityDelegation
MC_SECP256K1_PUB 0xe7 [0xe7, 0x01] 33 bytes SECG secp256k1 authentication, assertionMethod, capabilityInvocation, capabilityDelegation
MC_P384_PUB 0x1201 [0x81, 0x24] 49 bytes NIST P-384 Recognized as unsupported; document derived without key validation.
MC_P521_PUB 0x1202 [0x82, 0x24] 67 bytes NIST P-521 Recognized as unsupported; document derived without key validation.
MC_BLS12381_G2_PUB 0xeb [0xeb, 0x01] 96 bytes BLS12-381 G2 Recognized as unsupported; document derived without key validation.
MC_RSA_PUB 0x1205 [0x85, 0x24] Variable RSA 2048/4096 Recognized as unsupported; document derived without key validation.

2.1.2 Curve Point Validation & DoS Defenses

Section titled “2.1.2 Curve Point Validation & DoS Defenses”
  1. Length Enforcement:
    • Ed25519 and X25519 raw byte slices MUST measure exactly 32 bytes.
    • P-256 and secp256k1 raw byte slices MUST measure exactly 33 bytes representing a compressed SEC1 elliptic curve point.
  2. Point Validation:
    • Ed25519: Decoded via ed25519_dalek::VerifyingKey::from_bytes. Signature verification uses RFC 8032 verify_strict to prevent signature malleability and non-canonical point attacks.
    • P-256: Decoded via p256::ecdsa::VerifyingKey::from_sec1_bytes. SEC1 decompression validates that the point satisfies the Weierstrass equation $y^2 \equiv x^3 - 3x + b \pmod p$.
    • secp256k1: Decoded via k256::ecdsa::VerifyingKey::from_sec1_bytes. Point decompression validates that the coordinates lie on $y^2 \equiv x^3 + 7 \pmod p$.
    • Non-canonical encodings, coordinates exceeding field size, or points off curve return CryptoError::Malformed and abort resolution with ResolverError::InvalidDid.
  3. Key Agreement Isolation:
    • X25519 keys (0xec) populate solely the keyAgreement verification relationship. They are prohibited from asserting identity (authentication) or signing credentials (assertionMethod). Any signature verification attempted against an X25519 key fails with CryptoError::Malformed("X25519 is a key-agreement key and cannot verify signatures").

The did:jwk method packages a public JSON Web Key (RFC 7517) within a base64url-encoded string (did:jwk:<base64url-jwk>).

  • Length Constraint: The method-specific identifier string length is strictly capped at MAX_JWK_ENCODED_LEN = 4096. Identifiers exceeding this bound fail immediately with ResolverError::InvalidDid("did:jwk value is too long").
  • Duplicate Key Guard: Raw bytes are decoded using strict_json::from_slice(&bytes) to reject duplicate JSON keys at all nesting levels before deserialization.

2.2.2 Mandatory Private-Key Member Rejection

Section titled “2.2.2 Mandatory Private-Key Member Rejection”

To prevent private key leakage through public identifier reflection, DID.is enforces an absolute rejection policy. If any of the following private key members are present in the parsed JWK, resolution fails:

const PRIVATE_JWK_MEMBERS: &[&str] = &["d", "p", "q", "dp", "dq", "qi", "oth", "k"];

Private keys are never silently stripped or ignored. The presence of any member in PRIVATE_JWK_MEMBERS aborts resolution immediately with:

ResolverError::InvalidDid("did:jwk embeds private key material ('<member>'); refusing to resolve")

2.2.3 Point De-serialization & SEC1 Uncompressed Assembly

Section titled “2.2.3 Point De-serialization & SEC1 Uncompressed Assembly”

For EC keys (P-256 and secp256k1), x and y members (each exactly 32 base64url-decoded bytes) are parsed and concatenated into a 65-byte uncompressed SEC1 octet sequence: $$\text{SEC1} = \mathtt{0x04} \parallel x \parallel y$$ The point is then validated against the curve via from_sec1_bytes.

  • If use == "enc", the verification method is bound exclusively to keyAgreement.
  • If use == "sig", the verification method is bound to assertionMethod, authentication, capabilityInvocation, and capabilityDelegation.
  • If use is omitted, the method is bound to all relationships.

The did:web method maps a domain and optional path segments to a well-known HTTPS URL.

  • did:web:example.com $\to$ https://example.com/.well-known/did.json
  • did:web:example.com:user:alice $\to$ https://example.com/user/alice/did.json
  • did:web:example.com%3A8443 $\to$ https://example.com:8443/.well-known/did.json

2.3.2 Parsing Hardening & Anti-Evasion Rules

Section titled “2.3.2 Parsing Hardening & Anti-Evasion Rules”

The transformation pipeline strictly enforces:

  1. Single Percent-Decoding: Segment splitting occurs prior to percent-decoding the port component. Double percent-encoding (e.g. %253A) is rejected.
  2. IP Literal Prohibition: Numeric IP addresses (did:web:127.0.0.1, did:web:169.254.169.254), hexadecimal IPs (did:web:0x7f000001), octal representations (did:web:017700000001), and integer IP literals (did:web:2130706433) are rejected at the parser level with ResolverError::InvalidDid.
  3. Path Traversal & Ambiguity Guards: Colon segments containing dot segments (:..:, :%2E%2E:, :%2F..%2F) or userinfo markers (@, %40) are rejected.
  4. Strict Document ID Matching: Per W3C DID Resolution v1.0, the document retrieved from the remote host MUST contain a top-level id property that exactly equals the requested DID: $$\text{doc.id} \equiv \text{requested_did}$$ If doc.id != requested_did, resolution fails closed with ResolverError::InvalidDidDocument.

When resolving did:web, resolver-core validates domain authenticity via DIF Well Known DID Configuration (/.well-known/did-configuration.json):

  1. Fetches configuration from https://<origin>/.well-known/did-configuration.json.
  2. Inspects linked_dids[] array (up to MAX_LINKED_DIDS = 50).
  3. For each DomainLinkageCredential:
    • Validates that credentialSubject.id == issuer == did.
    • Normalizes and compares credentialSubject.origin with the request origin: $$\mathtt{normalize_origin(credentialSubject.origin)} \equiv \mathtt{normalize_origin(origin)}$$
    • Verifies the credential signature using a verification method declared in the DID document’s assertionMethod.
    • Validates temporal validity: validFrom / issuanceDate $\le \text{now} \le$ validUntil / expirationDate.

did:webvh (“DID Web + Verifiable History”) transforms mutable did:web infrastructure into a tamper-evident, cryptographically verifiable log of state transitions (did.jsonl).

Entry 1 (Genesis / Version 1):
SCID Calculation: multihash(sha2-256, JCS(Entry 1 with versionId="{SCID}", proof stripped))
Entry Hash: SCID
Proof: eddsa-jcs-2022 by updateKey
Commitment: nextKeyHashes = [hash(k2)]
│
▼ Linked by predecessor versionId & proof
Entry 2 (Update / Version 2):
Entry Hash: multihash(sha2-256, JCS(Entry 2 with versionId=Entry 1.versionId, proof stripped))
Proof: eddsa-jcs-2022 by k2 (satisfying nextKeyHashes)
Commitment: nextKeyHashes = [hash(k3)]

Entry digests use Base58BTC-encoded multihash format without a multibase prefix (starting with Qm): $$\text{multihash_b58}(D) = \mathtt{base58btc}([0x12, 0x20] \parallel \text{SHA-256}(D))$$ where 0x12 represents the sha2-256 multicodec and 0x20 specifies 32 bytes length.

The Self-Certifying Identifier (SCID) is deterministically generated from the genesis entry (entry 1):

  1. Strip the proof object from entry 1.
  2. Set versionId = "{SCID}".
  3. In the serialized JSON string, substitute all occurrences of the prospective SCID string with "{SCID}".
  4. Canonicalize using RFC 8785 (JCS).
  5. Compute: $$\text{SCID} = \text{multihash_b58}(\text{JCS}(\text{entry}_1[\text{versionId}\leftarrow\text{“{SCID}”}, \text{proof}\leftarrow\emptyset]))$$

For every subsequent entry $i > 1$:

  1. Strip the proof object from $\text{entry}_i$.
  2. Set versionId equal to the predecessor’s version identifier ($\text{versionId}_{i-1}$).
  3. Canonicalize using RFC 8785 (JCS).
  4. Compute: $$\text{entry_hash}_i = \text{multihash_b58}(\text{JCS}(\text{entry}i[\text{versionId}\leftarrow\text{versionId}{i-1}, \text{proof}\leftarrow\emptyset]))$$
  5. Invariants enforced:
    • Version numbers MUST increment sequentially by exactly 1 ($v_{i} = v_{i-1} + 1$).
    • versionTime MUST be strictly monotonically increasing ($t_i > t_{i-1}$).
    • Log entries MUST NOT exceed MAX_ENTRIES = 2000.
    • Total log stream size MUST NOT exceed MAX_LOG_BYTES = 4 \times 1024 \times 1024 (4 MiB).

To defend against server-compromise state substitutions:

  1. Entry $i$ declares nextKeyHashes = [H_1, H_2, \dots] where: $$H_k = \text{multihash_b58}(\text{MultikeyBytes})$$
  2. In entry $i+1$, the update key used to sign the state transition MUST satisfy: $$\text{multihash_b58}(\text{signer_multikey}) \in \text{nextKeyHashes}_i$$ Any update signed by a key not committed in the immediately preceding entry fails resolution.

When a witness policy is active:

  • Entry defines witness: { threshold: M, witnesses: [{ id: "did:key:..." }, ...] }.
  • Condition: $1 \le M \le N$ where $N$ is the witness count.
  • Witnesses publish detached Data Integrity proofs in /.well-known/did-witness.json (bounded to MAX_WITNESS_BYTES = 1024 \times 1024 bytes).
  • At least $M$ distinct witnesses MUST sign the entry hash. If fewer than $M$ valid signatures are present, resolution fails.