did:yadacoin is a Decentralized Identifier (DID) method anchored to the YadaCoin blockchain. Each identifier maps to a compressed secp256k1 public key. The corresponding DID Document is derived deterministically from that key's Key Event Log (KEL) — a chain of version-7 blockchain transactions that record key rotations, pre-rotation commitments, and optional relationship/scope data. This method supports creation, resolution, key rotation (update), and deactivation without any trusted registry beyond the YadaCoin peer-to-peer network.

This is an unofficial draft of the did:yadacoin DID Method Specification, version 1.0-draft, dated 2026-05-01. It is intended for submission to the W3C DID Extensions Registry. Feedback is welcome via the issue tracker.

Introduction

YadaCoin is a proof-of-work blockchain whose transaction format natively supports pre-rotation key management: each transaction commits a cryptographic hash of the next key before the current key is used, bounding the damage from key compromise. The KEL mechanism is a blockchain-anchored subset of [[KERI]] (Key Event Receipt Infrastructure).

did:yadacoin exposes this infrastructure through the [[DID-CORE]] specification, allowing any standard DID resolver to:

Design Goals

Goal How it is achieved
Decentralization No registry, DNS, or third-party service required for resolution.
Key rotation Pre-rotation commitments are bound on-chain before key exposure.
Revocation A key whose public_key_hash appears in the KEL is immediately revoked.
Minimal trust anchor Proof-of-work blockchain; no permissioned validator set.
Interoperability DIDs expressed as standard W3C DID URIs; DID Documents include JsonWebKey2020 and EcdsaSecp256k1VerificationKey2019 representations.

DID Method Name

The method name string that shall identify this DID method is: yadacoin.

A DID that uses this method MUST begin with the prefix did:yadacoin:.

Method-Specific Identifier

Syntax

did-yadacoin       = "did:yadacoin:" method-specific-id
method-specific-id = 66HEXDIG   ; 33-byte compressed secp256k1 public key, lowercase hex
HEXDIG             = DIGIT / "a" / "b" / "c" / "d" / "e" / "f"
DIGIT              = %x30-39

The method-specific identifier is the 66-character lowercase hexadecimal encoding of a compressed secp256k1 public key (33 bytes: a one-byte prefix 02 or 03 followed by the 32-byte X coordinate).

Example DIDs

did:yadacoin:02a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2
did:yadacoin:03f1e2d3c4b5a6978869504132beef01234567890abcdef01234567890abcdef0102

Address Derivation

A P2PKH address is derived from a public key using the Bitcoin-compatible procedure:

address = Base58Check( 0x00 || RIPEMD160( SHA256( compressed_public_key_bytes ) ) )

This address form is used within the KEL as public_key_hash, prerotated_key_hash, twice_prerotated_key_hash, and prev_public_key_hash.

Key Event Log (KEL)

The Key Event Log is an ordered list of version-7 YadaCoin transactions associated with a public key. Each entry records a key state transition.

KEL Entry Fields

Field Type Description
id string Transaction identifier (hex).
public_key string Compressed secp256k1 public key (hex) — the signer of this entry.
public_key_hash string P2PKH address of public_key. Once this value appears anywhere in the KEL the key is revoked.
prerotated_key_hash string P2PKH address of the next authorized signer.
twice_prerotated_key_hash string P2PKH address of the signer after next.
prev_public_key_hash string P2PKH address of the previous signer. Empty string ("") for the inception entry.
relationship string Base64-encoded UTF-8 JSON scope/relationship document (optional). Empty string ("") when unused.
transaction_signature string Signature over the serialized transaction by public_key.

Key Event Types

Type Location Description
Inception On-chain First entry for a public key. No prev_public_key_hash. Single output to prerotated_key_hash. relationship MUST be "".
Unconfirmed Mempool Rotation transaction that carries the optional scope/relationship. Always in mempool when active.
Confirming Mempool or on-chain Countersigns the unconfirmed rotation. relationship is "". Once confirmed, the new key is the head of the KEL.

Chain Integrity Rules

  1. kel[n+1].prev_public_key_hash == kel[n].public_key_hash for all n ≥ 0.
  2. kel[n+1].public_key corresponds to the key whose P2PKH address equals kel[n].prerotated_key_hash.
  3. A key is revoked if its P2PKH address appears as any public_key_hash in the KEL.
  4. The active key is the public_key of kel[-1] (the latest confirming entry).

DID Document

Production Rules

A DID Document for did:yadacoin:<pubkey_hex> is produced as follows:

  1. Resolve the KEL for <pubkey_hex> (see ).
  2. Determine the active key:
    • If the KEL is empty: the DID is unresolvable; return error: "notFound".
    • If addr(<pubkey_hex>) appears as a public_key_hash in the KEL: the key is revoked — return a deactivated DID Document (see ).
    • Otherwise: <pubkey_hex> is the active key and the DID Document is constructed as below.

DID Document Example

{
  "@context": [
    "https://www.w3.org/ns/did/v1",
    "https://w3id.org/security/suites/secp256k1-2019/v1",
    "https://w3id.org/security/suites/jws-2020/v1"
  ],
  "id": "did:yadacoin:02a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
  "verificationMethod": [
    {
      "id": "did:yadacoin:02a1b2c3...#key-1",
      "type": "EcdsaSecp256k1VerificationKey2019",
      "controller": "did:yadacoin:02a1b2c3...",
      "publicKeyHex": "02a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
    },
    {
      "id": "did:yadacoin:02a1b2c3...#jws-key-1",
      "type": "JsonWebKey2020",
      "controller": "did:yadacoin:02a1b2c3...",
      "publicKeyJwk": {
        "kty": "EC",
        "crv": "secp256k1",
        "x": "<base64url-encoded X coordinate>",
        "y": "<base64url-encoded Y coordinate>"
      }
    }
  ],
  "authentication": [
    "did:yadacoin:02a1b2c3...#key-1"
  ],
  "assertionMethod": [
    "did:yadacoin:02a1b2c3...#key-1"
  ],
  "keyAgreement": [],
  "yadacoinKel": {
    "depth": 3,
    "headTransactionId": "abcdef0123456789...",
    "prerotatedKeyHash": "1BpEi6DfDAUFd152iiBFiEkioYCf...",
    "twicePrerotatedKeyHash": "1AXm5SomeDifferentAddress..."
  }
}

The yadacoinKel Extension Property

The yadacoinKel property is a method-specific extension exposing the current KEL head state:

Field Description
depth Number of entries in the resolved KEL.
headTransactionId id field of the latest confirming KEL entry.
prerotatedKeyHash P2PKH address committed as the next signer.
twicePrerotatedKeyHash P2PKH address committed as the signer after next.

Resolvers that do not understand yadacoinKel MUST ignore it. This property is informational only and is derived from on-chain data.

CRUD Operations

Create

To create a did:yadacoin DID:

  1. Generate a compressed secp256k1 key pair (K₀_private, K₀_public).
  2. Pre-generate at least two additional key pairs (K₁, K₂) for pre-rotation.
  3. Compute P2PKH addresses: addr₀ = addr(K₀), addr₁ = addr(K₁), addr₂ = addr(K₂).
  4. Construct and broadcast an inception transaction (version-7) to the YadaCoin network:
    Field Value
    public_key K₀ (hex)
    public_key_hash addr₀
    prerotated_key_hash addr₁
    twice_prerotated_key_hash addr₂
    prev_public_key_hash ""
    relationship ""
    outputs Single output to addr₁
  5. Once the inception transaction is confirmed on-chain, the DID did:yadacoin:<K₀_hex> is created and resolvable.

The inception transaction MUST have exactly one output (sent to prerotated_key_hash), MUST have relationship == "", and MUST have prev_public_key_hash == "".

Read (Resolve)

To resolve did:yadacoin:<pubkey_hex>, retrieve the KEL using one of the following options:

Option A — Public REST API

GET https://yadacoin.io/key-event-log?public_key=<pubkey_hex>

Returns a JSON array of KEL entries ordered from oldest to newest. Apply the DID Document production rules in .

Option B — YadaCoin Python SDK

from yadacoin.core.keyeventlog import KeyEventLog

kel = await KeyEventLog.build_from_public_key(pubkey_hex)
# kel is an ordered list of KeyEvent objects

DID Resolution Metadata

The resolver SHOULD return the following resolution metadata:

Key Value
contentType "application/did+ld+json"
retrieved ISO 8601 timestamp of resolution.
error "notFound" if KEL is empty; "deactivated" if the key is revoked.

Update (Key Rotation)

To rotate the active key from Kₙ to Kₙ₊₁:

  1. Pre-generate additional key pairs Kₙ₊₂, Kₙ₊₃ as needed.
  2. Broadcast an unconfirmed rotation transaction (signed by Kₙ):
    Field Value
    public_key Kₙ (hex)
    public_key_hash addr(Kₙ)
    prerotated_key_hash addr(Kₙ₊₁)
    twice_prerotated_key_hash addr(Kₙ₊₂)
    prev_public_key_hash addr(Kₙ₋₁)
    relationship Base64-encoded JSON scope document (optional)
    outputs Sent to addr(Kₙ₊₁)
  3. Broadcast a confirming rotation transaction (signed by Kₙ₊₁):
    Field Value
    public_key Kₙ₊₁ (hex)
    public_key_hash addr(Kₙ₊₁)
    prerotated_key_hash addr(Kₙ₊₂)
    twice_prerotated_key_hash addr(Kₙ₊₃)
    prev_public_key_hash addr(Kₙ)
    relationship ""
    outputs Sent to addr(Kₙ₊₂)

After both transactions are accepted into the mempool (or confirmed on-chain), resolving did:yadacoin:<Kₙ₊₁_hex> returns the updated DID Document. The old DID did:yadacoin:<Kₙ_hex> resolves as revoked.

Mempool transactions are sufficient for resolution. Resolvers SHOULD check both mempool and confirmed chain entries to support agent credentials that have not yet been included in a block.

Deactivate

To deactivate a DID, the current key holder broadcasts a final rotation transaction that moves funds to a well-known burn address or a dedicated tombstone address with no corresponding private key. Once this transaction appears in the KEL the public_key_hash of the current key is present, causing all future resolutions to return a deactivated DID Document.

{
  "@context": ["https://www.w3.org/ns/did/v1"],
  "id": "did:yadacoin:<pubkey_hex>",
  "deactivated": true,
  "verificationMethod": [],
  "authentication": [],
  "assertionMethod": []
}

If the private key is destroyed without a rotation, the DID becomes permanently unresolvable-as-active. This is equivalent to abandonment, not a formal deactivation.

Security Considerations

Cryptographic Assumptions

This method relies on the security of secp256k1 ECDSA and the Bitcoin-compatible P2PKH address scheme (SHA-256 + RIPEMD-160). Both are widely deployed and peer-reviewed. Implementors SHOULD monitor NIST and IETF for guidance on post-quantum migration paths.

Key Compromise Before Rotation

If the active key Kₙ is compromised before the unconfirmed rotation transaction is broadcast, an attacker may attempt to rotate to a key they control. The pre-rotation commitment (prerotated_key_hash in the previous KEL entry) limits the scope of damage: the attacker must also know or derive the pre-committed Kₙ₊₁ key. Because Kₙ₊₁ was committed by hash before the compromise, the attacker cannot substitute their own key. This is the core security property of pre-rotation key management: commitment precedes exposure.

Blockchain Finality

YadaCoin uses proof-of-work consensus. Transactions with fewer than 6 confirmations SHOULD be treated as tentative. High-value operations SHOULD require more confirmations. Mempool-only entries are accepted for agent credential use cases but SHOULD NOT be relied upon for high-value or irreversible operations.

51% Attack

A majority hash-rate attacker could in principle rewrite KEL history. The same risk applies to all blockchain-anchored DID methods. Mitigations include requiring more confirmations, cross-anchoring to a higher-hashrate chain, or using witness receipts as in [[KERI]].

Key Recovery

This method does not support social recovery or multi-signature inception. If the active private key and all pre-rotated private keys are lost, the DID cannot be updated or deactivated. Implementors SHOULD store key material using hardware security modules (HSMs) or encrypted, redundant backups.

Replay Attacks

DID resolution is read-only; replay attacks do not directly apply. Applications built on top of did:yadacoin MUST implement their own replay protection (e.g., time-windowed challenges as specified in the YadaCoin KEL Agent Auth protocol).

Sybil Resistance

Creating a did:yadacoin DID requires broadcasting a transaction to the YadaCoin network, which entails a transaction fee. This provides modest Sybil resistance.

Privacy Considerations

Pseudonymity

did:yadacoin DIDs are pseudonymous. The method-specific identifier is a compressed public key and does not directly expose personal information. However, correlation of DIDs with on-chain transaction graphs, IP addresses, or application-layer data may allow de-anonymization.

Correlation

All KEL entries and their relationship fields are publicly readable on-chain. Implementors MUST NOT commit sensitive personal data in the relationship field. Scope documents SHOULD contain only the minimum information necessary for the intended authorization.

Personally Identifiable Information

The DID Document produced by this method does not include any PII. Application protocols that include PII in associated Verifiable Credentials MUST apply appropriate data minimization and purpose-limitation controls per applicable privacy regulations (e.g., GDPR, CCPA).

Selective Disclosure

This method does not natively support selective disclosure of DID Document attributes. Applications requiring selective disclosure SHOULD use Verifiable Presentations with BBS+ or SD-JWT signatures over Verifiable Credentials.

Key Rotation and Unlinkability

Each key rotation creates a new DID (new public key = new DID). Parties wishing to maintain unlinkability between interactions SHOULD use fresh DIDs for each relationship and SHOULD NOT reuse DIDs across contexts.

Verifiable Data Registry

The verifiable data registry for did:yadacoin is the YadaCoin blockchain — a public, proof-of-work distributed ledger. The network is permissionless; any node may participate in block production and validation.

Property Value
Network YadaCoin mainnet
Block time ~10 minutes
Transaction version 7 (key event transactions)
Public node https://yadacoin.io
KEL lookup endpoint GET https://yadacoin.io/key-event-log?public_key=<hex>

Conformance Requirements

This section is normative. The keywords MUST, MUST NOT, SHOULD, and MAY are to be interpreted as described in [[RFC2119]].

Conforming Resolver

A conforming did:yadacoin resolver:

  1. MUST implement the Read operation as specified in .
  2. MUST return "error": "notFound" when the KEL is empty.
  3. MUST return "deactivated": true when the resolved public key's address appears as a public_key_hash in the KEL.
  4. MUST check both the confirmed blockchain and the mempool when building the KEL.
  5. MUST apply the chain integrity rules in and discard invalid KEL entries.
  6. MUST NOT require the yadacoinKel extension property to produce a valid DID Document.

Conforming DID Controller

A conforming did:yadacoin DID controller:

  1. MUST use a compressed secp256k1 public key as the method-specific identifier.
  2. MUST pre-generate at least two additional key pairs before broadcasting the inception transaction.
  3. MUST NOT reuse a public key once its P2PKH address appears as a public_key_hash in the KEL.
  4. SHOULD use low-S normalized ECDSA signatures in all transactions.

Reference Implementation

Blockchain node / Python SDK
https://github.com/pdxwebdev/yadacoin — relevant modules: yadacoin.core.keyeventlog.KeyEventLog, yadacoin.core.identity.Identity, yadacoin.core.transaction.Transaction.
Agent auth SDK (Python)
yadacoin-agent-auth-py
Agent auth SDK (JavaScript)
yadacoin-agent-auth-js
Public resolver endpoint
GET https://yadacoin.io/key-event-log?public_key=<hex>

Revision History

Version Date Changes
1.0-draft 2026-05-01 Initial draft for W3C CCG submission.