@red-isbe/did-isbe-registry
v1.3.6
Published
https://github.com/alastria/isbe-contracts/tree/main/contracts/identity/didregistry
Downloads
38
Keywords
Readme
Alcance y fuentes de documentación
Esta librería es un wrapper TypeScript para facilitar el consumo de los smart contracts ISBE DID desde aplicaciones Node.js.
Su objetivo es simplificar la interacción técnica (construcción de transacciones, llamadas view, mapeo de estructuras y normalización de datos), no reemplazar ni duplicar la lógica de negocio completa definida en los contratos.
Qué sí cubre esta librería
- Encapsulación de llamadas a contratos ISBE.
- Construcción segura de transacciones (
rawTx) y llamadas de solo lectura. - Normalización de datos (
bytes32 ↔ fragment,hex ↔ JWK, etc.). - Validaciones técnicas y operativas básicas necesarias para el uso correcto de la API.
- Exposición de las reglas más relevantes y visibles para el consumidor de la librería.
Qué no cubre esta librería
- Reglas completas de negocio del protocolo DID ISBE.
- Validaciones internas profundas del smart contract.
- Políticas avanzadas, invariantes on-chain o restricciones de gobernanza.
- Detalles de implementación interna del contrato.
Estas reglas no se duplican aquí por diseño, para evitar inconsistencias entre cliente y contrato.
Documentación oficial y referencias
Para un entendimiento completo del comportamiento del sistema, se recomienda consultar:
Manual de la API ISBE DID (consumida por esta librería): URL del README / documentación de la API (Aquí se describen las reglas funcionales más relevantes desde la perspectiva del consumidor)
Repositorio de Smart Contracts ISBE DID: README del repositorio de contratos (Fuente de verdad para reglas de negocio, validaciones on-chain y lógica del protocolo)
Package & entorno de ejecución
Esta librería está publicada como ESM (ECMAScript Modules) y está pensada para ejecutarse en Node.js moderno con TypeScript.
Requisitos
- Node.js ≥ 18
- TypeScript ≥ 5.9
- Entorno compatible con ESM (
"type": "module")
Dependencias principales
- ethers v6 – Interacción con contratos inteligentes.
- bs58 – Codificación/decodificación base58 para fragmentos DID.
- elliptic – Soporte criptográfico para curvas elípticas.
- web3 – Compatibilidad con tooling adicional del ecosistema Ethereum.
Scripts disponibles
{
"test": "npm run test:unit; npm run test:esm",
"test:unit": "jest --config jest.config.ts",
"test:esm": "NODE_OPTIONS=--experimental-vm-modules jest -c jest.esm.config.ts",
"build": "tsc",
"start": "node dist/index.js"
}Tests
test:unitEjecuta los tests unitarios estándar (CommonJS / Jest clásico).test:esmEjecuta tests específicos que requieren ESM real, usando:NODE_OPTIONS=--experimental-vm-modules
Algunos módulos (por ejemplo
ConfigManager) interactúan con el sistema de archivos y ESM, por lo que requieren un runner específico para garantizar aislamiento y cobertura completa.
Instalación
npm install @red-isbe/did-isbe-registryConfiguración central (ConfigManager)
La librería utiliza un singleton de configuración para centralizar:
- Dirección del contrato DID Registry
- ABI del contrato
modelDeployId(usado para reconstruir DIDs legibles)- Provider RPC compartido
Archivo config.json
Existe en la raíz/src del proyecto y sigue esta estructura general:
{
"modelDeployId": "model-x",
"didRegistry": {
"address": "0x...",
"abiPath": "./IDidRegistry.json"
}
}ConfigManager
Responsabilidades principales:
- Cargar
config.jsondesde disco. - Instanciar el contrato
ethers.Contract. - Exponer getters consistentes para toda la librería.
- Permitir actualización dinámica en runtime (útil en entornos de test o multired).
Ejemplo de uso interno
const configManager = ConfigManager.getInstance(provider);
const contract = configManager.getContract();
const modelDeployId = configManager.getModelDeployId();Nota de diseño:
ConfigManageres un singleton deliberado. La librería asume una única configuración activa por proceso, alineada con el despliegue del contrato.
Utilidades de conversión DID / Verification Method
La carpeta utils contiene funciones puras y testeadas que normalizan los formatos exigidos por los contratos.
didAndVmethodConversion.ts
Este módulo encapsula toda la lógica de conversión entre formatos humanos y bytes32 on-chain, evitando duplicación y errores sutiles.
didToBytes32(did: string): string
Convierte correctamente:
- DID completo
did:isbe:<model>:<hex> - DID con fragmento
did:isbe:...#fragment - Hex directo
- Fragmento base58
Siempre retorna un bytes32 left-aligned + zero-padded, como esperan los contratos Solidity.
vMethodIdToBytes32(vMethodId: string): string
Convierte un identificador de método de verificación (did#fragment) a bytes32.
Validaciones incluidas
Estas funciones fallan temprano si el input es inválido:
- Hex no válido
- Fragmentos base58 corruptos
- Longitudes > 32 bytes
- Valores incompatibles con Solidity
Esto reduce errores on-chain y fallos difíciles de depurar.
Filosofía de diseño
- Una sola fuente de verdad para conversiones binarias.
- Errores explícitos en lugar de silencios peligrosos.
- Cero dependencia de estado (excepto
ConfigManager). - Totalmente testeadas con cobertura de ramas y errores.
Utilidades criptográficas (jwkConversion)
Este módulo convierte claves públicas entre:
- JWK (JsonWebKey) usado en APIs / DID Core
- HEX (XY concatenado) usado por los contratos (64 bytes = 32 X + 32 Y)
jwkToHex(jwk)
Convierte un objeto JWK EC a 0x... (64 bytes XY).
Valida:
- Formato de objeto
- Existencia de
xyy - Tamaño exacto: 32 bytes cada uno (curvas secp256k1 / secp256r1)
Si la curva no es reconocida, imprime un warning pero exporta igualmente.
hexToJwk(hex, ellipticType)
Convierte 0x + 64 bytes a JWK EC.
La curva se determina por ellipticType:
0→none1→secp256k12→secp256r1
Nota: el contrato define la semántica de curva por
ellipticType, el formato binario XY es el mismo.
Construcción y envío de transacciones (sendTransaction)
Este módulo abstrae la construcción de transacciones “listas para firmar” y su envío posterior.
buildRawTransaction(contract, method, args, provider)
- Codifica calldata usando
contract.interface.encodeFunctionData. - Resuelve
toconcontract.getAddress(). - Estima gas con
provider.estimateGas, con fallback a5_000_000. - Obtiene
chainIdconprovider.getNetwork(). - Construye una transacción EIP-155 y retorna el
unsignedSerialized.
Implementación actual:
gasPricese fija comoBigInt(4000).
sendSignedTransaction(rawTx, provider)
- Emite el rawTx firmado con
provider.broadcastTransaction. - Espera confirmación con
.wait(). - Retorna
TransactionReceipt(onullsi el provider lo retorna así).
Módulo: IDidController
IDidController encapsula operaciones relacionadas con controladores de un DID, incluyendo:
- Construcción de transacciones para agregar / revocar controllers.
- Consultas de controladores autorizados.
- Consulta paginada de DIDs asociados a un controller.
Métodos
Transaccionales (devuelven rawTx)
buildAddControllerTx(did, controller): Promise<string>buildRevokeControllerTx(did, controller): Promise<string>sendSignedTransaction(rawTx): Promise<...>
Ambos builders convierten did y controller a bytes32 usando didToBytes32(...), y delegan en buildRawTransaction(...).
Lectura (view)
checkController(did, controller): Promise<boolean>- Usa el selector del contrato:
"checkController(bytes32,address)"
- Usa el selector del contrato:
checkControllerBytes(didBytes, controller): Promise<boolean>- Variante bytes:
"checkController(bytes,address)"
- Variante bytes:
getDidsByController(controller, page, pageSize): Promise<GetDidsByControllerResult>- Hace
encodeFunctionData+provider.cally decodifica el resultado. - Reconstruye DIDs legibles con el
modelDeployIddeconfig.json.
- Hace
Tipo de retorno para paginación
export type GetDidsByControllerResult = {
items: string[];
total: string;
howMany: string;
prev: string;
next: string;
};Ejemplo rápido de uso: controllers
import { JsonRpcProvider } from "ethers";
import IDidController from "did-isbe-registry/dist/identity/did-isbe-lib/IDidController.js";
const provider = new JsonRpcProvider("https://tu-nodo-rpc");
const ctrl = new IDidController(provider);
// 1) Crear rawTx para agregar un controller
const rawTx = await ctrl.buildAddControllerTx(
"did:isbe:model-x:0123456789abcdef0123456789abcdef01234567",
"did:isbe:model-x:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
);
// 2) Enviar una transacción firmada
// (la firma se hace fuera de la lib; aquí solo se emite el rawTx ya firmado)
const receipt = await ctrl.sendSignedTransaction("0xSIGNED_RAW_TX");
// 3) Consultar
const ok = await ctrl.checkController(
"did:isbe:model-x:0123456789abcdef0123456789abcdef01234567",
"0xabc123...def456" // address EVM del controller
);
// 4) Paginación
const dids = await ctrl.getDidsByController(
"did:isbe:model-x:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
0,
10
);
console.log(dids.items);Módulo: IDidVerificationMethod
IDidVerificationMethod encapsula la gestión de Verification Methods de un DID (alta, revocación, expiración y rotación), y construye transacciones listas para firmar (rawTx).
Métodos
Transaccionales (devuelven rawTx)
buildAddVerificationMethodTx(did, vMethodId, publicKey, ellipticType): Promise<string>publicKeydebe ser un string JSON válido representando un JWK.- Convierte
publicKey (JWK)→pubHex (0xXY)usandojwkToHex. - Convierte
didyvMethodIdabytes32usandodidToBytes32yvMethodIdToBytes32. - Construye la tx para
addVerificationMethod(didBytes32, vMethodIdBytes32, pubHex, ellipticType).
buildRevokeVerificationMethodTx(did, vMethodId, notAfter): Promise<string>- Construye la tx para
revokeVerificationMethod(didBytes32, vMethodIdBytes32, notAfter).
- Construye la tx para
buildExpireVerificationMethodTx(did, vMethodId, notAfter): Promise<string>- Construye la tx para
expireVerificationMethod(didBytes32, vMethodIdBytes32, notAfter).
- Construye la tx para
buildRollVerificationMethodTx(args: RollArgs): Promise<string>- Construye la tx para
rollVerificationMethod(rollStruct)(struct). - Antes de construirla, aplica una validación de política (ver abajo).
- Construye la tx para
sendSignedTransaction(rawTx): Promise<...>- Delegación a
sendSignedTransaction(rawTx, provider).
- Delegación a
Tipo RollArgs
export type RollArgs = {
did: string;
vMethodId: string; // nuevo vMethodId (did#fragment o fragment)
publicKey: string; // JWK en string JSON
ellipticType: number; // 0=NONE, 1=K1, 2=R1 (según contrato)
notBefore: number;
notAfter: number;
oldVMethodId: string; // vMethodId actual a rotar
duration: number;
};Regla de negocio aplicada en rotación (política)
Al ejecutar buildRollVerificationMethodTx, la librería aplica una política específica:
Solo si el
oldVMethodIdpertenece a la relacióncapabilityInvocation, entonces:- No se permite cambiar la curva (
ellipticType) durante la rotación. - Si intenta rotar a otra curva, lanza:
- No se permite cambiar la curva (
Rotación inválida: el método <oldVMethodId> pertenece a 'capabilityInvocation' con curva <currentCurve>, no puede rotarse a <newEllipticType>.
Esto se valida consultando el DID Document crudo vía getDidDocument y reconstruyendo:
vMethodIdsText(did#fragment) desde bytes32→base58 (helper internobytes32ToFragment)vRelationshipspara detectar si el método está vinculado acapabilityInvocationvMethods[idx].ellipticTypepara comparar la curva actual vs nueva
Importante: si el método no es
capabilityInvocation, la librería no aplica restricción (se permite cambiar curva).
Ejemplo: add / revoke / expire
import { JsonRpcProvider } from "ethers";
import IDidVerificationMethod from "did-isbe-registry/dist/identity/did-isbe-lib/IDidVerificationMethod.js";
const provider = new JsonRpcProvider("https://tu-rpc");
const vm = new IDidVerificationMethod(provider);
const rawAdd = await vm.buildAddVerificationMethodTx(
"did:isbe:model-x:0123456789abcdef0123456789abcdef01234567",
"did:isbe:model-x:0123456789abcdef0123456789abcdef01234567#zQm...",
JSON.stringify({ kty:"EC", crv:"secp256k1", x:"...", y:"..." }),
1
);
const rawRevoke = await vm.buildRevokeVerificationMethodTx(
"did:isbe:model-x:0123456789abcdef0123456789abcdef01234567",
"did:isbe:model-x:...#zQm...",
1735689600
);
const rawExpire = await vm.buildExpireVerificationMethodTx(
"did:isbe:model-x:0123456789abcdef0123456789abcdef01234567",
"did:isbe:model-x:...#zQm...",
1735689600
);Ejemplo: roll (rotación)
const rawRoll = await vm.buildRollVerificationMethodTx({
did: "did:isbe:model-x:0123456789abcdef0123456789abcdef01234567",
vMethodId: "did:isbe:model-x:...#NEW_FRAG",
publicKey: JSON.stringify({ kty:"EC", crv:"secp256k1", x:"...", y:"..." }),
ellipticType: 1,
notBefore: 1700000000,
notAfter: 1800000000,
oldVMethodId: "did:isbe:model-x:...#OLD_FRAG",
duration: 31536000,
});Módulo: IDidVerificationRelationship
IDidVerificationRelationship permite crear relaciones de verificación (authentication, assertionMethod, capabilityInvocation, etc.) entre un DID y un Verification Method, y consultar DIDs asociados a un método/relación de forma paginada.
Tipos
export type DidWithPeriod = {
did: string;
notBefore: string;
notAfter: String; // (nota: en el código es String)
};
export type GetDidsByVerificationRelationshipResult = {
items: DidWithPeriod[];
total: number;
howMany: number;
prev: number;
next: number;
};Métodos
Transaccionales (devuelven rawTx)
buildAddVerificationRelationshipTx(did, name, vMethodId, notBefore, notAfter): Promise<string>Convierte
didyvMethodIdabytes32Construye la tx:
addVerificationRelationship(didBytes32, name, vMethodIdBytes32, notBefore, notAfter)
sendSignedTransaction(rawTx): Promise<...>- Delegación a
sendSignedTransaction(rawTx, provider).
- Delegación a
Lectura (view)
getDidsByVerificationRelationship(vMethodId, name, page, pageSize): Promise<GetDidsByVerificationRelationshipResult>- Llama a
getDidsByVerificationRelationship(vMethodIdBytes32, name, page, pageSize). - Reconstruye
did:isbe:<modelDeployId>:<hex>desde bytes32 (removiendo trailing zeros). - Retorna paginación (
total/howMany/prev/next) comonumber.
- Llama a
Ejemplo: crear relación
import { JsonRpcProvider } from "ethers";
import IDidVerificationRelationship from "did-isbe-registry/dist/identity/did-isbe-lib/IDidVerificationRelationship.js";
const provider = new JsonRpcProvider("https://tu-rpc");
const rel = new IDidVerificationRelationship(provider);
const rawTx = await rel.buildAddVerificationRelationshipTx(
"did:isbe:model-x:0123456789abcdef0123456789abcdef01234567",
"authentication",
"did:isbe:model-x:...#zQmFragment",
1700000000,
1800000000
);Ejemplo: consulta paginada
const result = await rel.getDidsByVerificationRelationship(
"did:isbe:model-x:...#zQmFragment",
"authentication",
0,
10
);
console.log(result.items); // [{ did, notBefore, notAfter }, ...]Módulo: IDidDocumentDetailed
IDidDocumentDetailed es el wrapper de más alto nivel para inicializar el registro, insertar/actualizar DID Documents y consultar DID Documents (en formato interno y en formato W3C).
A diferencia de los otros módulos que reciben un JsonRpcProvider, este módulo recibe un rpcUrl y crea internamente un ethers.JsonRpcProvider.
Tipos principales
DidDocument: representación interna (mapeada directo desde el contrato).DidW3CDocument: documento compatible con W3C DID Core, construido a partir del interno.GetDidsResult: paginación de DIDs existentes.
Métodos
Inicialización / escritura (transaccionales → devuelven rawTx)
buildInitializeDiDRegistryTx(ellipticType): Promise<string>- Construye tx para
initializeDiDRegistry(ellipticType).
- Construye tx para
buildInsertDidDocumentTx(did, baseDocument, fragment, publicKey, ellipticType, notBefore, notAfter): Promise<string>publicKeydebe ser un string JSON válido representando un JWK.- Convierte
publicKey (JWK)→pubHex (0xXY)usandojwkToHex. didse convierte abytes32(requiere method-specific-id de 40 hex).fragmentse interpreta como base58, se decodifica y se paddea abytes32.
buildInsertFirstDidDocumentTx(did, baseDocument, fragment, proof, publicKey, ellipticType, notBefore, notAfter, alsoKnownAs): Promise<string>- Variante “first insert” que incluye
proofyalsoKnownAs.
- Variante “first insert” que incluye
buildUpdateBaseDocumentTx(did, baseDocument): Promise<string>- Tx para
updateBaseDocument(didBytes32, baseDocument).
- Tx para
buildUpdateAlsoKnownAsTx(did, alsoKnownAs): Promise<string>- Tx para
updateAlsoKnownAs(didBytes32, alsoKnownAs).
- Tx para
sendSignedTransaction(rawTx)- Delegación a
sendSignedTransaction(rawTx, provider).
- Delegación a
Lectura / consultas
getDids(page, pageSize): Promise<GetDidsResult>- Llama el método view
getDids. - Reconstruye
did:isbe:<modelDeployId>:<hex>quitando trailing zeros delbytes32.
- Llama el método view
getDidDocument(did): Promise<DidW3CDocument>- Obtiene el documento en estructura interna (
getDidDocumentW3C) y lo transforma a W3C con “now” =Date.now()/1000.
- Obtiene el documento en estructura interna (
getDidDocumentByTimestamp(did, timestamp): Promise<DidW3CDocument>- Igual que
getDidDocument, pero el filtrado de relaciones se hace con el timestamp dado.
- Igual que
getDidDocumentW3C(did): Promise<DidDocument>- Llama el método view
getDidDocument(didBytes32)y mapea a estructura interna.
- Llama el método view
getDidDocumentByTimestampW3C(did, timestamp): Promise<DidDocument>- Llama el método view
getDidDocumentByTimestamp(didBytes32, timestamp)y mapea a estructura interna.
- Llama el método view
Helpers públicos (útiles para tests / integraciones)
didToBytes32(did)fragmentToBytes32(fragment)bytes32ToFragment(b32)buildVMethodId(did, fragment)→${did}#${fragment}
Formatos y validaciones relevantes
DID → bytes32
didToBytes32exige:- DID no vacío
method-specific-iden el último:y debe ser 40 hex
Se convierte a bytes32 con padding a la derecha.
Ejemplo de DID válido:
did:isbe:model-x:0123456789abcdef0123456789abcdef01234567
fragment (base58) → bytes32
fragmentToBytes32(fragment):- decodifica base58
- valida longitud
<= 32 bytes - padRight a 32 bytes
Si excede 32 bytes lanza:
Fragmento demasiado largo (>32 bytes)
bytes32 → fragment (base58)
bytes32ToFragmentelimina trailing zeros antes de codificar base58.
Transformación a W3C DID Document (DidW3CDocument)
Cuando llamas getDidDocument o getDidDocumentByTimestamp, la librería:
Parsea
baseDocument(que viene como JSON string desde contrato) y lo “mezcla” (...baseObj) en el retorno.Convierte
controllers(bytes/hex) a DID legible:did:isbe:<modelDeployId>:<first40hex>
Construye
verificationMethodconJsonWebKey2020:id = internal.vMethodIds[i]publicKeyJwk = vm.publicKey(ya convertido desdehexToJwk)
Construye relaciones DID Core de forma dinámica:
Solo incluye relaciones permitidas:
authentication,assertionMethod,keyAgreement,capabilityInvocation,capabilityDelegation
Filtra por ventana activa:
- se incluye solo si
now ∈ [notBefore, notAfter]
- se incluye solo si
Ignora relaciones no estándar.
Nota:
alsoKnownAssolo se incluye si viene con elementos (si el array viene vacío, el campo no aparece en el resultado).
Ejemplos de uso
Insert / update
import IDidDocumentDetailed from "did-isbe-registry/dist/identity/did-isbe-lib/IDidDocumentDetailed.js";
const lib = new IDidDocumentDetailed("https://tu-rpc");
// initialize
const rawInit = await lib.buildInitializeDiDRegistryTx(1);
// insert
const rawInsert = await lib.buildInsertDidDocumentTx(
"did:isbe:model-x:0123456789abcdef0123456789abcdef01234567",
JSON.stringify({ "@context": ["https://www.w3.org/ns/did/v1"] }),
"zQmFragmentBase58",
JSON.stringify({ kty:"EC", crv:"secp256k1", x:"...", y:"..." }),
1,
1700000000,
1800000000
);
// update baseDocument
const rawUpdateBase = await lib.buildUpdateBaseDocumentTx(
"did:isbe:model-x:0123456789abcdef0123456789abcdef01234567",
JSON.stringify({ "@context": ["https://www.w3.org/ns/did/v1"], custom: "v2" })
);Get DID Document (W3C)
const w3cDoc = await lib.getDidDocument(
"did:isbe:model-x:0123456789abcdef0123456789abcdef01234567"
);
console.log(w3cDoc.id);
console.log(w3cDoc.verificationMethod);
console.log(w3cDoc.authentication);Get DID Document by timestamp
const ts = 1_700_000_000;
const w3cDocAtTs = await lib.getDidDocumentByTimestamp(
"did:isbe:model-x:0123456789abcdef0123456789abcdef01234567",
ts
);
// relaciones ya filtradas según ts
console.log(w3cDocAtTs.capabilityInvocation);Notas finales
Esta librería asume que el contrato ISBE DID Registry ya está desplegado y accesible vía RPC.
El archivo
config.jsones la pieza central de configuración, ya que define:modelDeployId- dirección del contrato DID Registry
- ruta al ABI correspondiente
Las validaciones realizadas en la librería son estructurales y de formato, no de consenso ni de autorización final.
Para comprender qué está permitido o prohibido a nivel de identidad, controladores, relaciones o rotaciones de clave, consulta siempre:
© Licencia
Apache-2.0
Copyright © 2025 Comunidad de Madrid & Alastria
