Skip to content

Latest commit

 

History

History
150 lines (116 loc) · 5.58 KB

File metadata and controls

150 lines (116 loc) · 5.58 KB

The did:stellar Method

Reference: did:stellar v0.1 specification

What is a did:stellar?

A Decentralized Identifier anchored on the Stellar network. An identity is materialized as an opaque 128-bit identifier registered in a Soroban smart contract. The method is independent of any specific issuer, wallet-agnostic, and compliant with W3C DID Core 1.1.

DID Syntax

did:stellar:{network}:{didId}
Component Value Example
network mainnet or testnet testnet
didId 16 random bytes encoded as base32 lowercase, exactly 26 characters znfxngsh46vkyqu6inrx4omphi

Validation regex (from src/identifier.ts):

^did:stellar:(mainnet|testnet):[a-z2-7]{26}$

Constants (from src/identifier.ts):

  • DID_ID_BYTES = 16 — raw byte length
  • DID_ID_LENGTH = 26 — base32 string length

Why the DID is NOT the wallet

A did:stellar is intentionally not derived from a Stellar account. The 128-bit didId is generated by the client using CSPRNG and has no mathematical relationship to any G... address. This means:

  • The DID survives key rotation. If the controller account is compromised, call transferController to a new wallet — the DID remains the same.
  • Multiple DIDs can be controlled by the same wallet.
  • The wallet appears only as controller inside the on-chain DidRecord, not in the DID string itself.

On-chain registry

Field Value
Contract name did-stellar-registry
Testnet ID CB7ATU7SF5QUKJMSULJDJVWJZVDXC23HTZX6NFUDTSFPVT6MA575NNZJ
Mainnet ID Not deployed yet
Storage Soroban persistent storage, one entry per DID
Read path getLedgerEntries via Stellar RPC (free, no tx fee)

DidRecord structure

Stored on-chain as DidDataKey::Record(BytesN<16>):

Field Type Constraint
controller Stellar G... address Classic accounts only in v0.1
authentication Array of DidKey 1–3 keys (minimum 1 required)
assertionMethod Array of DidKey 0–3 keys
keyAgreement Array of DidKey 0–1 key
services Array of DidService 0–3 entries
metadataUri Optional string HTTPS URL, max 255 chars
metadataHash Optional 32 bytes SHA-256 of the metadata payload
version u32 Starts at 1, increments on every mutation
createdLedger u32 Ledger number at registration (immutable)
updatedLedger u32 Ledger number of last mutation
deactivated bool One-way flag, cannot be reset

Validation limits (from src/record/types.ts)

Limit Value
MAX_KEY_MULTIBASE_LEN 128 chars
MIN_KEY_COUNT_AUTH 1
MAX_KEY_COUNT_AUTH 3
MAX_KEY_COUNT_ASSERT 3
MAX_KEY_COUNT_AGREEMENT 1
MAX_SERVICE_COUNT 3
MAX_SERVICE_ID_LEN 32 chars
MAX_SERVICE_TYPE_LEN 64 chars
MAX_URL_LEN 255 chars
METADATA_HASH_LEN 32 bytes

Contract operations

Operation Authorization Optimistic concurrency
register(did_id, initial_record) initial_record.controller must sign No (new entry)
update(did_id, expected_version, next_record) Current controller must sign Yes — expected_version must match
transfer_controller(did_id, expected_version, new_controller) Current controller must sign Yes
deactivate(did_id, expected_version) Current controller must sign Yes
get(did_id) None (read-only) No

DID Document

When a DidRecord is read from the chain, the SDK constructs a W3C DID Document following these rules:

Rule Implementation
@context ["https://www.w3.org/ns/did/v1", "https://w3id.org/security/multikey/v1"] for active; ["https://www.w3.org/ns/did/v1"] for tombstone
No root controller field Self-controlled per W3C DID Core 1.1 §5.1.2
verificationMethod One entry per key, type: "Multikey", controller: <the DID itself>
Verification relationships Fragment references only (#auth-1, #assert-1, #keyagr-1), never inlined keys
Services Fragment #service-{idSuffix}

Key types (Multikey encoding)

Relationship Curve Multibase prefix Example
authentication, assertionMethod Ed25519 z6Mk z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doY
keyAgreement X25519 z6LS z6LSnGSQaEk7SBZMmMLHTCqz6YUuiVVCmBNdAqSVdepqYAW1

Tombstone (deactivated DID)

When a DID is deactivated, the document becomes:

{
  "@context": ["https://www.w3.org/ns/did/v1"],
  "id": "did:stellar:testnet:...",
  "verificationMethod": [],
  "authentication": [],
  "assertionMethod": [],
  "keyAgreement": [],
  "service": []
}

HTTP resolver returns this with status 410 Gone.

Proof of Control

The protocol for verifying off-chain DID control (e.g., DID-based login):

  1. Verifier sends a challenge: { did, domain, nonce, timestamp }
  2. Signer canonicalizes with JCS (RFC 8785), signs with Ed25519
  3. Verifier checks: timestamp ±5 min → domain match → nonce uniqueness → signature against authentication keys

The SDK implements this as buildChallenge() + verifyProofOfControl().

Trust model

  • Any verifier can resolve a DID using only a Stellar RPC URL and the registry contract ID
  • No ACTA infrastructure is required for resolution
  • did.acta.build is a convenience wrapper; the SDK talks directly to Stellar RPC
  • The HTTP service has no authentication — trust-minimised by design