DID

Registre et Resolver

L'état de chaque did:stellar vit dans le contrat Soroban did-stellar-registry. Le resolver hébergé sur `https://did.acta.build` est une commodité HTTP sans état par-dessus : il résout les DIDs, prépare des XDR non signés et soumet les XDR signés. Il n'exige aucune authentification et ne détient jamais de clés.

Registre sur la chaîne

  • Une entrée par DID dans le stockage persistant Soroban, indexée par l'identifiant DID de 16 octets.
  • Les lectures sont gratuites : la résolution utilise getLedgerEntries sur le RPC Stellar, sans transaction ni frais.
  • Le contrat est un *magasin passif* : il persiste exactement ce que register/update envoient. Il ne remplit jamais de valeurs par défaut et n'invente pas de clés. Les champs de tenue de registre (version, createdLedger, updatedLedger, deactivated) appartiennent au contrat et écrasent tout ce que l'appelant passe.

L'enregistrement DID

ChampTypeContrainte
controllerAdresse StellarComptes classiques G... uniquement (v0.1)
authenticationDidKey[]1 à 3 clés (au moins 1 requise)
assertionMethodDidKey[]0 à 3 clés
keyAgreementDidKey[]0 ou 1 clé
servicesDidService[]0 à 3 entrées
metadataUrichaîne optionnelleHTTPS uniquement, max 255 caractères
metadataHash32 octets optionnelsSHA-256 ; exige metadataUri
versionu32Démarre à 1, +1 par mutation
createdLedger / updatedLedgeru32Numéros de séquence du ledger
deactivatedboolDrapeau à sens unique

DidKey est { publicKeyMultibase }. DidService est { idSuffix, serviceType, serviceEndpoint }, exposé dans le document sous la forme {did}#service-{idSuffix}.

Limites de l'enregistrement

LimiteValeur
Longueur du multibase des clésmax 128 caractères
idSuffix de servicemax 32 caractères, ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$
serviceType de servicemax 64 caractères, non vide
URLs de service / métadonnéesHTTPS uniquement, max 255 caractères

Opérations du contrat

OpérationAuthConcurrence optimiste
register(did_id, initial_record)initial_record.controller signeNon (nouvelle entrée)
update(did_id, expected_version, next_record)Contrôleur actuelOui
transfer_controller(did_id, expected_version, new_controller)Contrôleur actuelOui
deactivate(did_id, expected_version)Contrôleur actuelOui
get(did_id)Aucune (lecture)Non

Les codes d'erreur du contrat sont documentés dans (table du registre did:stellar).

Contrats déployés

RéseauID du contrat de registre
TestnetCB7ATU7SF5QUKJMSULJDJVWJZVDXC23HTZX6NFUDTSFPVT6MA575NNZJ
MainnetCD6LSWW5ZSXOO5WAIHKQLQ262TW7BPI37PNEVMMA273BAPC65NN2AYXQ

Ce sont les registres utilisés par did.acta.build, l'API ACTA et les valeurs par défaut des bibliothèques.

Resolver hébergé (did.acta.build)

  • Aucune authentification et aucune garde de clés : il ne manipule que des XDR non signés et signés.
  • Multi-réseau : un seul déploiement sert testnet et mainnet, en routant chaque requête selon le réseau contenu dans le DID.
  • Sans état et horizontalement scalable ; les résultats de résolution sont en cache ~30 secondes.

Endpoints

MéthodeCheminRôle
GET/healthVivacité + réseaux configurés
GET/docsSwagger UI
GET/openapi.jsonSpécification OpenAPI 3.1
GET/1.0/identifiers/{did}Endpoint DIF Universal Resolver (résolution DID W3C)
GET/v1/dids/stellar/{did}Enregistrement DID brut sur la chaîne
POST/v1/dids/stellarEnregistrement (prepare/submit)
POST/v1/dids/stellar/{did}/updateMise à jour (prepare/submit)
POST/v1/dids/stellar/{did}/transferTransfert de contrôleur (prepare/submit)
POST/v1/dids/stellar/{did}/deactivateDésactivation, irréversible (prepare/submit)
POST/v1/dids/stellar/submitSoumettre n'importe quel XDR signé

Endpoint de résolution

GET /1.0/identifiers/{did}

Retourne le résultat de résolution DID W3C (didDocument + didDocumentMetadata + didResolutionMetadata). Négociation de contenu : application/did+ld+json (par défaut, avec @context) ou application/did+json.

bash
curl https://did.acta.build/1.0/identifiers/did:stellar:testnet:znfxngsh46vkyqu6inrx4omphi
StatutSignification
200DID actif résolu
400DID invalide (didResolutionMetadata.error: "invalidDid")
404Non enregistré
410Désactivé (document pierre tombale)
502RPC Stellar injoignable

GET /v1/dids/stellar/{did}

Retourne l'enregistrement brut sur la chaîne sans enveloppe W3C : { did, didId, record: { controller, authentication, assertionMethod, keyAgreement, services, version, createdLedger, updatedLedger, deactivated } }. Utilisez-le pour lire la version actuelle avant une mise à jour, ou directement le controller.

Endpoints de mutation

Toutes les mutations sont en POST et suivent le même schéma prepare/submit utilisé dans tout ACTA.

Enregistrement (prepare) :

bash
curl -X POST https://did.acta.build/v1/dids/stellar \
  -H "Content-Type: application/json" \
  -d '{
    "did": "did:stellar:testnet:...",
    "sourcePublicKey": "G...",
    "record": {
      "controller": "G...",
      "authentication": [{ "publicKeyMultibase": "z6Mk..." }],
      "assertionMethod": [{ "publicKeyMultibase": "z6Mk..." }],
      "keyAgreement": [],
      "services": []
    }
  }'

Réponse : { "xdr": "...", "network": "testnet", "networkPassphrase": "..." }

Submit (après signature avec le wallet du contrôleur) :

bash
curl -X POST https://did.acta.build/v1/dids/stellar/submit \
  -H "Content-Type: application/json" \
  -d '{ "signedXdr": "AAAA..." }'

Réponse : { "txId": "..." }

  • Mise à jour : POST /v1/dids/stellar/{did}/update avec { expectedVersion, sourcePublicKey, record }. Remplacement complet de l'enregistrement ; une version périmée retourne 409 version_mismatch.
  • Transfert : POST /v1/dids/stellar/{did}/transfer avec { expectedVersion, newController, sourcePublicKey }.
  • Désactivation : POST /v1/dids/stellar/{did}/deactivate avec { expectedVersion, sourcePublicKey }. Ensuite le DID se résout avec 410.

Prepare/Submit

Le mode est sélectionné par le corps de la requête : si signedXdr est présent, la route soumet ; sans lui, la route valide et retourne le xdr non signé plus la passphrase du réseau. Le wallet Stellar du contrôleur signe l'enveloppe de transaction ; les clés DID à l'intérieur de l'enregistrement sont un matériel distinct (elles signent les credentials et les défis de Proof of Control, jamais la transaction d'enregistrement).

Limites de débit et erreurs

  • Limite de débit par IP : 120 requêtes par 60 secondes par défaut, avec les en-têtes X-RateLimit-Limit, X-RateLimit-Remaining et Retry-After ; 429 retourne { "code": "rate_limited" }.
  • Chaque erreur utilise une enveloppe stable { code, message, details? }code est une chaîne lisible par machine, partagée avec la bibliothèque TypeScript (par exemple did_invalid, did_already_exists, version_mismatch, did_deactivated, tx_submission_failed). Branchez-vous sur code, jamais sur message.
  • Les requêtes portent un en-tête de corrélation X-Request-ID, renvoyé en écho dans les réponses.