@red-isbe/isbe-service-did-registry
v1.0.0
Published
A library for interacting with the ISBE Service DID Registry: operational identities under did:isbe:svc:
Readme
@red-isbe/isbe-service-did-registry
Client library for the ISBE Service DID Registry: operational identities — services,
pipelines, agents — under the did:isbe:svc: sub-namespace, controlled by an
organisational did:isbe.
The registry is a facet of the same EIP-2535 Diamond the organisational DID Registry lives in, so this library points at the same address. What differs is the ABI each one carries.
Install
npm install @red-isbe/isbe-service-did-registryUse
import {
ServiceDidRegistryContext,
ServiceDidRegistryQueries,
ServiceDidRegistryTransactions,
} from "@red-isbe/isbe-service-did-registry";
const context = new ServiceDidRegistryContext(
provider, // ethers JsonRpcProvider
"uc-dev", // model deploy id
diamondAddress,
1, // network curve: 1 = secp256k1, 2 = P-256
);
// Writes return an UNSIGNED transaction. This library never holds a key.
const tx = await new ServiceDidRegistryTransactions(context)
.buildRegisterServiceDidTx(controllerDid, publicKey, 1, "billing pipeline", 0, from);
// …the caller signs it, broadcasts it, and then:
const doc = await new ServiceDidRegistryQueries(context).toDidDocumentW3C(serviceDid);Every write builder returns the transaction fields and stops there. from is used only to
look up that account's nonce — it authorises nothing. Who may perform the call is decided
on-chain, from the signature.
What it offers
| | |
|---|---|
| ServiceDidRegistryContext | Provider, Diamond address, network discriminator and curve |
| ServiceDidRegistryTransactions | buildRegisterServiceDidTx, buildUpdateServiceDocumentTx, buildRotateSigningKeyTx, buildUpdateExpiryTx, buildDeactivateServiceDidTx, buildInitializeServiceDidRegistryTx |
| ServiceDidRegistryQueries | getServiceDid, getServiceDocument, getServiceDidsByController, isServiceDidActive, signingKeyAddressOf, computeServiceDid, toDidDocumentW3C |
| ServiceDidTransactionExecutor | Broadcasts a signed transaction and waits for its receipt |
| Derivation helpers | buildServiceDid, parseServiceDid, computeServiceDidHex, serviceDidToHex, signingKeyAddress, toUncompressedPublicKey |
Service documents
On top of the fixed minimum — controller, key, curve, label, expiry — each service may carry any fields its organisation wants, as one JSON object:
await transactions.buildRegisterServiceDidTx(
controllerDid, publicKey, 1, "billing pipeline", 0, from,
{ serviceEndpoint: "https://billing.example.com", companyName: "Example Ltd" },
);
await transactions.buildUpdateServiceDocumentTx(serviceDid, { serviceEndpoint: "https://new.example.com" }, from);It is stored on-chain and toDidDocumentW3C adds its fields to the DID Document. It may
not set the members the registry derives (RESERVED_DOCUMENT_KEYS: id, controller,
the keys and relationships…), it must fit in 4096 bytes, and it is public and permanent
in the chain history. An update replaces the whole document; null clears it.
The identifier
On-chain a service identity is keyed by keccak256(abi.encode(controllerDid, uint64 nonce))
— a full digest, not invertible. The readable form therefore carries its own pre-image:
did:isbe:svc:<network>:<parent segment>:<nonce>so the string can be turned back into the digest with no network call, and so a reader can
see which organisation owns it. computeServiceDidHex and parseServiceDid are the two
halves of that translation.
Resolution
toDidDocumentW3C produces a W3C DID Document. The verification method follows the curve
of the key, not the network: on the network's own curve it carries a blockchainAccountId,
on any other curve the coordinates as a JWK — publishing an address for a key no account
can use would point a verifier at something that can never sign. The declared @context
follows that same branch.
A service also stops resolving as valid when its controlling organisation stops being operative. That cascade is evaluated only if the caller supplies it, because reading the organisational registry is not this library's job:
await queries.toDidDocumentW3C(serviceDid, { isControllerActive, now });The document then comes back marked deactivated: true with a reason —
deactivated, expired or controller-inactive — so a caller can tell "this service was
revoked" from "this service is fine but its organisation is not".
Relationship with @red-isbe/did-isbe-registry
This package is independent: it imports nothing from the organisational registry library, and a consumer that only deals with service identities does not need it.
What the two share is the encoding of an organisational DID into its bytes32 form. Those
functions — didToHex and hexToDid — are duplicated here rather than depended upon, and
are exported so the equivalence can be checked from outside. They must never diverge: a
service registered through one package would become unresolvable through the other, with
nothing failing loudly. src/utils/__test__/did.utils.test.ts pins the behaviour with
vectors read from the DEV network, not computed by the functions themselves.
Tests
npm test53 tests across two suites. The ESM one exists because multiformats is ESM-only and the
base58 encoding has to be exercised for real, not mocked.
Requirement
The Diamond must have the Service DID Registry facet cut into it. Without it every call from this library reverts.
