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.
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:
| 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.
|
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:.
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).
did:yadacoin:02a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2 did:yadacoin:03f1e2d3c4b5a6978869504132beef01234567890abcdef01234567890abcdef0102
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.
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.
| 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.
|
| 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.
|
kel[n+1].prev_public_key_hash ==
kel[n].public_key_hash
for all n ≥ 0.
kel[n+1].public_key
corresponds to the key whose
P2PKH address equals
kel[n].prerotated_key_hash.
public_key_hash in the
KEL.
public_key of
kel[-1] (the latest
confirming entry).
A DID Document for
did:yadacoin:<pubkey_hex>
is produced as follows:
<pubkey_hex> (see
).
error: "notFound".
addr(<pubkey_hex>)
appears as a
public_key_hash in
the KEL: the key is
revoked — return a
deactivated DID Document (see
).
<pubkey_hex> is
the active key and the DID
Document is constructed as below.
{
"@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..."
}
}
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.
To create a
did:yadacoin DID:
(K₀_private, K₀_public).
(K₁, K₂) for
pre-rotation.
addr₀ = addr(K₀),
addr₁ = addr(K₁),
addr₂ = addr(K₂).
| 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₁
|
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 == "".
To resolve
did:yadacoin:<pubkey_hex>, retrieve the KEL using one of the
following options:
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 .
from yadacoin.core.keyeventlog import KeyEventLog kel = await KeyEventLog.build_from_public_key(pubkey_hex) # kel is an ordered list of KeyEvent objects
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.
|
To rotate the active key from
Kₙ to Kₙ₊₁:
Kₙ₊₂,
Kₙ₊₃ as needed.
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ₙ₊₁)
|
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.
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.
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.
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.
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.
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]].
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.
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).
Creating a did:yadacoin DID
requires broadcasting a transaction to
the YadaCoin network, which entails a
transaction fee. This provides modest
Sybil resistance.
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.
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.
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).
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.
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.
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>
|
This section is normative. The keywords MUST, MUST NOT, SHOULD, and MAY are to be interpreted as described in [[RFC2119]].
A conforming
did:yadacoin resolver:
"error": "notFound" when
the KEL is empty.
"deactivated": true when
the resolved public key's address
appears as a
public_key_hash in the
KEL.
yadacoinKel extension
property to produce a valid DID
Document.
A conforming
did:yadacoin DID
controller:
public_key_hash in the
KEL.
yadacoin.core.keyeventlog.KeyEventLog,
yadacoin.core.identity.Identity,
yadacoin.core.transaction.Transaction.
GET
https://yadacoin.io/key-event-log?public_key=<hex>
| Version | Date | Changes |
|---|---|---|
| 1.0-draft | 2026-05-01 | Initial draft for W3C CCG submission. |