Add comprehensive installation and setup documentation

- Add GETTING_STARTED.md with quick start guide and development modes
- Add INSTALL.sh automated installation script
- Add INSTALLATION_CHECKLIST.md, INSTALLATION_SUCCESS.md, and INSTALLATION_SUMMARY.md
- Add QUICK_REFERENCE.md for common commands
- Add SETUP_GUIDE.md with detailed setup instructions
- Update README.md with improved project overview
- Add did-wallet app dependencies and node_modules
This commit is contained in:
Dorian
2026-01-27 17:18:21 +00:00
parent a81f655133
commit 0d073fa89e
22658 changed files with 4494151 additions and 6 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+570
View File
@@ -0,0 +1,570 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { IonDid, IonRequest } from '@decentralized-identity/ion-sdk';
import { LocalKeyManager, computeJwkThumbprint } from '@web5/crypto';
import { Did } from '../did.js';
import { BearerDid } from '../bearer-did.js';
import { DidMethod } from '../methods/did-method.js';
import { DidError, DidErrorCode } from '../did-error.js';
import { getVerificationRelationshipsById } from '../utils.js';
import { EMPTY_DID_RESOLUTION_RESULT } from '../types/did-resolution.js';
/**
* Enumerates the types of keys that can be used in a DID ION document.
*
* The DID ION method supports various cryptographic key types. These key types are essential for
* the creation and management of DIDs and their associated cryptographic operations like signing
* and encryption.
*/
export var DidIonRegisteredKeyType;
(function (DidIonRegisteredKeyType) {
/**
* Ed25519: A public-key signature system using the EdDSA (Edwards-curve Digital Signature
* Algorithm) and Curve25519.
*/
DidIonRegisteredKeyType["Ed25519"] = "Ed25519";
/**
* secp256k1: A cryptographic curve used for digital signatures in a range of decentralized
* systems.
*/
DidIonRegisteredKeyType["secp256k1"] = "secp256k1";
/**
* secp256r1: Also known as P-256 or prime256v1, this curve is used for cryptographic operations
* and is widely supported in various cryptographic libraries and standards.
*/
DidIonRegisteredKeyType["secp256r1"] = "secp256r1";
/**
* X25519: A Diffie-Hellman key exchange algorithm using Curve25519.
*/
DidIonRegisteredKeyType["X25519"] = "X25519";
})(DidIonRegisteredKeyType || (DidIonRegisteredKeyType = {}));
/**
* Private helper that maps algorithm identifiers to their corresponding DID ION
* {@link DidIonRegisteredKeyType | registered key type}.
*/
const AlgorithmToKeyTypeMap = {
Ed25519: DidIonRegisteredKeyType.Ed25519,
ES256K: DidIonRegisteredKeyType.secp256k1,
ES256: DidIonRegisteredKeyType.secp256r1,
'P-256': DidIonRegisteredKeyType.secp256r1,
secp256k1: DidIonRegisteredKeyType.secp256k1,
secp256r1: DidIonRegisteredKeyType.secp256r1
};
/**
* The default node to use as a gateway to the Sidetree newtork when anchoring, updating, and
* resolving DID documents.
*/
const DEFAULT_GATEWAY_URI = 'https://ion.tbd.engineering';
/**
* The `DidIon` class provides an implementation of the `did:ion` DID method.
*
* Features:
* - DID Creation: Create new `did:ion` DIDs.
* - DID Key Management: Instantiate a DID object from an existing key in a Key Management System
* (KMS). If supported by the KMS, a DID's key can be exported to a portable
* DID format.
* - DID Resolution: Resolve a `did:ion` to its corresponding DID Document stored in the Sidetree
* network.
* - Signature Operations: Sign and verify messages using keys associated with a DID.
*
* @see {@link https://identity.foundation/sidetree/spec/ | Sidetree Protocol Specification}
* @see {@link https://github.com/decentralized-identity/ion/blob/master/docs/design.md | ION Design Document}
*
* @example
* ```ts
* // DID Creation
* const did = await DidIon.create();
*
* // DID Creation with a KMS
* const keyManager = new LocalKeyManager();
* const did = await DidIon.create({ keyManager });
*
* // DID Resolution
* const resolutionResult = await DidIon.resolve({ did: did.uri });
*
* // Signature Operations
* const signer = await did.getSigner();
* const signature = await signer.sign({ data: new TextEncoder().encode('Message') });
* const isValid = await signer.verify({ data: new TextEncoder().encode('Message'), signature });
*
* // Key Management
*
* // Instantiate a DID object for a published DID with existing keys in a KMS
* const did = await DidIon.fromKeyManager({
* didUri: 'did:ion:EiAzB7K-xDIKc1csXo5HX2eNBoemK9feNhL3cKwfukYOug',
* keyManager
* });
*
* // Convert a DID object to a portable format
* const portableDid = await DidIon.toKeys({ did });
* ```
*/
export class DidIon extends DidMethod {
/**
* Creates a new DID using the `did:ion` method formed from a newly generated key.
*
* Notes:
* - If no `options` are given, by default a new Ed25519 key will be generated.
*
* @example
* ```ts
* // DID Creation
* const did = await DidIon.create();
*
* // DID Creation with a KMS
* const keyManager = new LocalKeyManager();
* const did = await DidIon.create({ keyManager });
* ```
*
* @param params - The parameters for the create operation.
* @param params.keyManager - Optionally specify a Key Management System (KMS) used to generate
* keys and sign data.
* @param params.options - Optional parameters that can be specified when creating a new DID.
* @returns A Promise resolving to a {@link BearerDid} object representing the new DID.
*/
static create() {
return __awaiter(this, arguments, void 0, function* ({ keyManager = new LocalKeyManager(), options = {} } = {}) {
// Before processing the create operation, validate DID-method-specific requirements to prevent
// keys from being generated unnecessarily.
var _a, _b, _c, _d, _e, _f, _g;
// Check 1: Validate that the algorithm for any given verification method is supported by the
// DID ION specification.
if ((_a = options.verificationMethods) === null || _a === void 0 ? void 0 : _a.some(vm => !(vm.algorithm in AlgorithmToKeyTypeMap))) {
throw new Error('One or more verification method algorithms are not supported');
}
// Check 2: Validate that the ID for any given verification method is unique.
const methodIds = (_b = options.verificationMethods) === null || _b === void 0 ? void 0 : _b.filter(vm => 'id' in vm).map(vm => vm.id);
if (methodIds && methodIds.length !== new Set(methodIds).size) {
throw new Error('One or more verification method IDs are not unique');
}
// Check 3: Validate that the required properties for any given services are present.
if ((_c = options.services) === null || _c === void 0 ? void 0 : _c.some(s => !s.id || !s.type || !s.serviceEndpoint)) {
throw new Error('One or more services are missing required properties');
}
// If no verification methods were specified, generate a default Ed25519 verification method.
const defaultVerificationMethod = {
algorithm: 'Ed25519',
purposes: ['authentication', 'assertionMethod', 'capabilityDelegation', 'capabilityInvocation']
};
const verificationMethodsToAdd = [];
// Generate random key material for additional verification methods, if any.
for (const vm of (_d = options.verificationMethods) !== null && _d !== void 0 ? _d : [defaultVerificationMethod]) {
// Generate a random key for the verification method.
const keyUri = yield keyManager.generateKey({ algorithm: vm.algorithm });
const publicKey = yield keyManager.getPublicKey({ keyUri });
// Add the verification method to the DID document.
verificationMethodsToAdd.push({
id: vm.id,
publicKeyJwk: publicKey,
purposes: (_e = vm.purposes) !== null && _e !== void 0 ? _e : ['authentication', 'assertionMethod', 'capabilityDelegation', 'capabilityInvocation']
});
}
// Generate a random key for the ION Recovery Key. Sidetree requires secp256k1 recovery keys.
const recoveryKeyUri = yield keyManager.generateKey({ algorithm: DidIonRegisteredKeyType.secp256k1 });
const recoveryKey = yield keyManager.getPublicKey({ keyUri: recoveryKeyUri });
// Generate a random key for the ION Update Key. Sidetree requires secp256k1 update keys.
const updateKeyUri = yield keyManager.generateKey({ algorithm: DidIonRegisteredKeyType.secp256k1 });
const updateKey = yield keyManager.getPublicKey({ keyUri: updateKeyUri });
// Compute the Long Form DID URI from the keys and services, if any.
const longFormDidUri = yield DidIonUtils.computeLongFormDidUri({
recoveryKey,
updateKey,
services: (_f = options.services) !== null && _f !== void 0 ? _f : [],
verificationMethods: verificationMethodsToAdd
});
// Expand the DID URI string to a DID document.
const { didDocument, didResolutionMetadata } = yield DidIon.resolve(longFormDidUri, { gatewayUri: options.gatewayUri });
if (didDocument === null) {
throw new Error(`Unable to resolve DID during creation: ${didResolutionMetadata === null || didResolutionMetadata === void 0 ? void 0 : didResolutionMetadata.error}`);
}
// Create the BearerDid object, including the "Short Form" of the DID URI, the ION update and
// recovery keys, and specifying that the DID has not yet been published.
const did = new BearerDid({
uri: longFormDidUri,
document: didDocument,
metadata: {
published: false,
canonicalId: longFormDidUri.split(':', 3).join(':'),
recoveryKey,
updateKey
},
keyManager
});
// By default, publish the DID document to a Sidetree node unless explicitly disabled.
if ((_g = options.publish) !== null && _g !== void 0 ? _g : true) {
const registrationResult = yield DidIon.publish({ did, gatewayUri: options.gatewayUri });
did.metadata = registrationResult.didDocumentMetadata;
}
return did;
});
}
/**
* Given the W3C DID Document of a `did:ion` DID, return the verification method that will be used
* for signing messages and credentials. If given, the `methodId` parameter is used to select the
* verification method. If not given, the first verification method in the authentication property
* in the DID Document is used.
*
* @param params - The parameters for the `getSigningMethod` operation.
* @param params.didDocument - DID Document to get the verification method from.
* @param params.methodId - ID of the verification method to use for signing.
* @returns Verification method to use for signing.
*/
static getSigningMethod(_a) {
return __awaiter(this, arguments, void 0, function* ({ didDocument, methodId }) {
var _b;
// Verify the DID method is supported.
const parsedDid = Did.parse(didDocument.id);
if (parsedDid && parsedDid.method !== this.methodName) {
throw new DidError(DidErrorCode.MethodNotSupported, `Method not supported: ${parsedDid.method}`);
}
// Get the verification method with either the specified ID or the first assertion method.
const verificationMethod = (_b = didDocument.verificationMethod) === null || _b === void 0 ? void 0 : _b.find(vm => { var _a; return vm.id === (methodId !== null && methodId !== void 0 ? methodId : (_a = didDocument.assertionMethod) === null || _a === void 0 ? void 0 : _a[0]); });
if (!(verificationMethod && verificationMethod.publicKeyJwk)) {
throw new DidError(DidErrorCode.InternalError, 'A verification method intended for signing could not be determined from the DID Document');
}
return verificationMethod;
});
}
/**
* Instantiates a {@link BearerDid} object for the DID ION method from a given {@link PortableDid}.
*
* This method allows for the creation of a `BearerDid` object using a previously created DID's
* key material, DID document, and metadata.
*
* @example
* ```ts
* // Export an existing BearerDid to PortableDid format.
* const portableDid = await did.export();
* // Reconstruct a BearerDid object from the PortableDid.
* const did = await DidIon.import({ portableDid });
* ```
*
* @param params - The parameters for the import operation.
* @param params.portableDid - The PortableDid object to import.
* @param params.keyManager - Optionally specify an external Key Management System (KMS) used to
* generate keys and sign data. If not given, a new
* {@link LocalKeyManager} instance will be created and
* used.
* @returns A Promise resolving to a `BearerDid` object representing the DID formed from the
* provided PortableDid.
* @throws An error if the DID document does not contain any verification methods or the keys for
* any verification method are missing in the key manager.
*/
static import(_a) {
return __awaiter(this, arguments, void 0, function* ({ portableDid, keyManager = new LocalKeyManager() }) {
// Verify the DID method is supported.
const parsedDid = Did.parse(portableDid.uri);
if ((parsedDid === null || parsedDid === void 0 ? void 0 : parsedDid.method) !== DidIon.methodName) {
throw new DidError(DidErrorCode.MethodNotSupported, `Method not supported`);
}
const did = yield BearerDid.import({ portableDid, keyManager });
return did;
});
}
/**
* Publishes a DID to a Sidetree node, making it publicly discoverable and resolvable.
*
* This method handles the publication of a DID Document associated with a `did:ion` DID to a
* Sidetree node.
*
* @remarks
* - This method is typically invoked automatically during the creation of a new DID unless the
* `publish` option is set to `false`.
* - For existing, unpublished DIDs, it can be used to publish the DID Document to a Sidetree node.
* - The method relies on the specified Sidetree node to interface with the network.
*
* @param params - The parameters for the `publish` operation.
* @param params.did - The `BearerDid` object representing the DID to be published.
* @param params.gatewayUri - Optional. The URI of a server involved in executing DID
* method operations. In the context of publishing, the
* endpoint is expected to be a Sidetree node. If not
* specified, a default node is used.
* @returns A Promise resolving to a boolean indicating whether the publication was successful.
*
* @example
* ```ts
* // Generate a new DID and keys but explicitly disable publishing.
* const did = await DidIon.create({ options: { publish: false } });
* // Publish the DID to the Sidetree network.
* const isPublished = await DidIon.publish({ did });
* // `isPublished` is true if the DID was successfully published.
* ```
*/
static publish(_a) {
return __awaiter(this, arguments, void 0, function* ({ did, gatewayUri = DEFAULT_GATEWAY_URI }) {
var _b, _c, _d;
// Construct an ION verification method made up of the id, public key, and purposes from each
// verification method in the DID document.
const verificationMethods = (_c = (_b = did.document.verificationMethod) === null || _b === void 0 ? void 0 : _b.map(vm => ({
id: vm.id,
publicKeyJwk: vm.publicKeyJwk,
purposes: getVerificationRelationshipsById({ didDocument: did.document, methodId: vm.id })
}))) !== null && _c !== void 0 ? _c : [];
// Create the ION document.
const ionDocument = yield DidIonUtils.createIonDocument({
services: (_d = did.document.service) !== null && _d !== void 0 ? _d : [],
verificationMethods
});
// Construct the ION Create Operation request.
const createOperation = yield DidIonUtils.constructCreateRequest({
ionDocument,
recoveryKey: did.metadata.recoveryKey,
updateKey: did.metadata.updateKey
});
try {
// Construct the URL of the SideTree node's operations endpoint.
const operationsUrl = DidIonUtils.appendPathToUrl({
baseUrl: gatewayUri,
path: `/operations`
});
// Submit the Create Operation to the operations endpoint.
const response = yield fetch(operationsUrl, {
method: 'POST',
mode: 'cors',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(createOperation)
});
// Return the result of processing the Create operation, including the updated DID metadata
// with the publishing result.
return {
didDocument: did.document,
didDocumentMetadata: Object.assign(Object.assign({}, did.metadata), { published: response.ok }),
didRegistrationMetadata: {}
};
}
catch (error) {
return {
didDocument: null,
didDocumentMetadata: {
published: false,
},
didRegistrationMetadata: {
error: DidErrorCode.InternalError,
errorMessage: `Failed to publish DID document for: ${did.uri}`
}
};
}
});
}
/**
* Resolves a `did:ion` identifier to its corresponding DID document.
*
* This method performs the resolution of a `did:ion` DID, retrieving its DID Document from the
* Sidetree-based DID overlay network. The process involves querying a Sidetree node to retrieve
* the DID Document that corresponds to the given DID identifier.
*
* @remarks
* - If a `gatewayUri` option is not specified, a default node is used to access the Sidetree
* network.
* - It decodes the DID identifier and retrieves the associated DID Document and metadata.
* - In case of resolution failure, appropriate error information is returned.
*
* @example
* ```ts
* const resolutionResult = await DidIon.resolve('did:ion:example');
* ```
*
* @param didUri - The DID to be resolved.
* @param options - Optional parameters for resolving the DID. Unused by this DID method.
* @returns A Promise resolving to a {@link DidResolutionResult} object representing the result of the resolution.
*/
static resolve(didUri_1) {
return __awaiter(this, arguments, void 0, function* (didUri, options = {}) {
var _a, _b;
// Attempt to parse the DID URI.
const parsedDid = Did.parse(didUri);
// If parsing failed, the DID is invalid.
if (!parsedDid) {
return Object.assign(Object.assign({}, EMPTY_DID_RESOLUTION_RESULT), { didResolutionMetadata: { error: 'invalidDid' } });
}
// If the DID method is not "ion", return an error.
if (parsedDid.method !== DidIon.methodName) {
return Object.assign(Object.assign({}, EMPTY_DID_RESOLUTION_RESULT), { didResolutionMetadata: { error: 'methodNotSupported' } });
}
// To execute the read method operation, use the given gateway URI or a default Sidetree node.
const gatewayUri = (_a = options === null || options === void 0 ? void 0 : options.gatewayUri) !== null && _a !== void 0 ? _a : DEFAULT_GATEWAY_URI;
try {
// Construct the URL to be used in the resolution request.
const resolutionUrl = DidIonUtils.appendPathToUrl({
baseUrl: gatewayUri,
path: `/identifiers/${didUri}`
});
// Attempt to retrieve the DID document and metadata from the Sidetree node.
const response = yield fetch(resolutionUrl);
// If the DID document was not found, return an error.
if (!response.ok) {
throw new DidError(DidErrorCode.NotFound, `Unable to find DID document for: ${didUri}`);
}
// If the DID document was retrieved successfully, return it.
const { didDocument, didDocumentMetadata } = yield response.json();
return Object.assign(Object.assign(Object.assign({}, EMPTY_DID_RESOLUTION_RESULT), didDocument && { didDocument }), { didDocumentMetadata: Object.assign({ published: (_b = didDocumentMetadata === null || didDocumentMetadata === void 0 ? void 0 : didDocumentMetadata.method) === null || _b === void 0 ? void 0 : _b.published }, didDocumentMetadata) });
}
catch (error) {
// Rethrow any unexpected errors that are not a `DidError`.
if (!(error instanceof DidError))
throw new Error(error);
// Return a DID Resolution Result with the appropriate error code.
return Object.assign(Object.assign({}, EMPTY_DID_RESOLUTION_RESULT), { didResolutionMetadata: Object.assign({ error: error.code }, error.message && { errorMessage: error.message }) });
}
});
}
}
/**
* Name of the DID method, as defined in the DID ION specification.
*/
DidIon.methodName = 'ion';
/**
* The `DidIonUtils` class provides utility functions to support operations in the DID ION method.
*/
export class DidIonUtils {
/**
* Appends a specified path to a base URL, ensuring proper formatting of the resulting URL.
*
* This method is useful for constructing URLs for accessing various endpoints, such as Sidetree
* nodes in the ION network. It handles the nuances of URL path concatenation, including the
* addition or removal of leading/trailing slashes, to create a well-formed URL.
*
* @param params - The parameters for URL construction.
* @param params.baseUrl - The base URL to which the path will be appended.
* @param params.path - The path to append to the base URL.
* @returns The fully constructed URL string with the path appended to the base URL.
*/
static appendPathToUrl({ baseUrl, path }) {
const url = new URL(baseUrl);
url.pathname = url.pathname.endsWith('/') ? url.pathname : url.pathname + '/';
url.pathname += path.startsWith('/') ? path.substring(1) : path;
return url.toString();
}
/**
* Computes the Long Form DID URI given an ION DID's recovery key, update key, services, and
* verification methods.
*
* @param params - The parameters for computing the Long Form DID URI.
* @param params.recoveryKey - The ION Recovery Key.
* @param params.updateKey - The ION Update Key.
* @param params.services - An array of services associated with the DID.
* @param params.verificationMethods - An array of verification methods associated with the DID.
* @returns A Promise resolving to the Long Form DID URI.
*/
static computeLongFormDidUri(_a) {
return __awaiter(this, arguments, void 0, function* ({ recoveryKey, updateKey, services, verificationMethods }) {
// Create the ION document.
const ionDocument = yield DidIonUtils.createIonDocument({ services, verificationMethods });
// Normalize JWK to onnly include specific members and in lexicographic order.
const normalizedRecoveryKey = DidIonUtils.normalizeJwk(recoveryKey);
const normalizedUpdateKey = DidIonUtils.normalizeJwk(updateKey);
// Compute the Long Form DID URI.
const longFormDidUri = yield IonDid.createLongFormDid({
document: ionDocument,
recoveryKey: normalizedRecoveryKey,
updateKey: normalizedUpdateKey
});
return longFormDidUri;
});
}
/**
* Constructs a Sidetree Create Operation request for a DID document within the ION network.
*
* This method prepares the necessary payload for submitting a Create Operation to a Sidetree
* node, encapsulating the details of the DID document, recovery key, and update key.
*
* @param params - Parameters required to construct the Create Operation request.
* @param params.ionDocument - The DID document model containing public keys and service endpoints.
* @param params.recoveryKey - The recovery public key in JWK format.
* @param params.updateKey - The update public key in JWK format.
* @returns A promise resolving to the ION Create Operation request model, ready for submission to a Sidetree node.
*/
static constructCreateRequest(_a) {
return __awaiter(this, arguments, void 0, function* ({ ionDocument, recoveryKey, updateKey }) {
// Create an ION DID create request operation.
const createRequest = yield IonRequest.createCreateRequest({
document: ionDocument,
recoveryKey: DidIonUtils.normalizeJwk(recoveryKey),
updateKey: DidIonUtils.normalizeJwk(updateKey)
});
return createRequest;
});
}
/**
* Assembles an ION document model from provided services and verification methods
*
* This model serves as the foundation for a DID document in the ION network, facilitating the
* creation and management of decentralized identities. It translates service endpoints and
* public keys into a format compatible with the Sidetree protocol, ensuring the resulting DID
* document adheres to the required specifications for ION DIDs. This method is essential for
* constructing the payload needed to register or update DIDs within the ION network.
*
* @param params - The parameters containing the services and verification methods to include in the ION document.
* @param params.services - A list of service endpoints to be included in the DID document, specifying ways to interact with the DID subject.
* @param params.verificationMethods - A list of verification methods to be included, detailing the cryptographic keys and their intended uses within the DID document.
* @returns A Promise resolving to an `IonDocumentModel`, ready for use in Sidetree operations like DID creation and updates.
*/
static createIonDocument(_a) {
return __awaiter(this, arguments, void 0, function* ({ services, verificationMethods }) {
var _b, _c;
/**
* STEP 1: Convert verification methods to ION SDK format.
*/
const ionPublicKeys = [];
for (const vm of verificationMethods) {
// Use the given ID, the key's ID, or the key's thumbprint as the verification method ID.
let methodId = (_c = (_b = vm.id) !== null && _b !== void 0 ? _b : vm.publicKeyJwk.kid) !== null && _c !== void 0 ? _c : yield computeJwkThumbprint({ jwk: vm.publicKeyJwk });
methodId = `${methodId.split('#').pop()}`; // Remove fragment prefix, if any.
// Convert public key JWK to ION format.
const publicKey = {
id: methodId,
publicKeyJwk: DidIonUtils.normalizeJwk(vm.publicKeyJwk),
purposes: vm.purposes,
type: 'JsonWebKey2020'
};
ionPublicKeys.push(publicKey);
}
/**
* STEP 2: Convert service entries, if any, to ION SDK format.
*/
const ionServices = services.map(service => (Object.assign(Object.assign({}, service), { id: `${service.id.split('#').pop()}` // Remove fragment prefix, if any.
})));
/**
* STEP 3: Format as ION document.
*/
const ionDocumentModel = {
publicKeys: ionPublicKeys,
services: ionServices
};
return ionDocumentModel;
});
}
/**
* Normalize the given JWK to include only specific members and in lexicographic order.
*
* @param jwk - The JWK to normalize.
* @returns The normalized JWK.
*/
static normalizeJwk(jwk) {
const keyType = jwk.kty;
let normalizedJwk;
if (keyType === 'EC') {
normalizedJwk = { crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y };
}
else if (keyType === 'oct') {
normalizedJwk = { k: jwk.k, kty: jwk.kty };
}
else if (keyType === 'OKP') {
normalizedJwk = { crv: jwk.crv, kty: jwk.kty, x: jwk.x };
}
else if (keyType === 'RSA') {
normalizedJwk = { e: jwk.e, kty: jwk.kty, n: jwk.n };
}
else {
throw new Error(`Unsupported key type: ${keyType}`);
}
return normalizedJwk;
}
}
//# sourceMappingURL=did-ion.js.map
File diff suppressed because one or more lines are too long
+298
View File
@@ -0,0 +1,298 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { Convert } from '@web5/common';
import { LocalKeyManager } from '@web5/crypto';
import { Did } from '../did.js';
import { DidMethod } from './did-method.js';
import { BearerDid } from '../bearer-did.js';
import { DidError, DidErrorCode } from '../did-error.js';
import { EMPTY_DID_RESOLUTION_RESULT } from '../types/did-resolution.js';
/**
* The `DidJwk` class provides an implementation of the `did:jwk` DID method.
*
* Features:
* - DID Creation: Create new `did:jwk` DIDs.
* - DID Key Management: Instantiate a DID object from an existing verification method key set or
* or a key in a Key Management System (KMS). If supported by the KMS, a DID's
* key can be exported to a portable DID format.
* - DID Resolution: Resolve a `did:jwk` to its corresponding DID Document.
* - Signature Operations: Sign and verify messages using keys associated with a DID.
*
* @remarks
* The `did:jwk` DID method uses a single JSON Web Key (JWK) to generate a DID and does not rely
* on any external system such as a blockchain or centralized database. This characteristic makes
* it suitable for use cases where a assertions about a DID Subject can be self-verifiable by
* third parties.
*
* The DID URI is formed by Base64URL-encoding the JWK and prefixing with `did:jwk:`. The DID
* Document of a `did:jwk` DID contains a single verification method, which is the JWK used
* to generate the DID. The verification method is identified by the key ID `#0`.
*
* @see {@link https://github.com/quartzjer/did-jwk/blob/main/spec.md | DID JWK Specification}
*
* @example
* ```ts
* // DID Creation
* const did = await DidJwk.create();
*
* // DID Creation with a KMS
* const keyManager = new LocalKeyManager();
* const did = await DidJwk.create({ keyManager });
*
* // DID Resolution
* const resolutionResult = await DidJwk.resolve({ did: did.uri });
*
* // Signature Operations
* const signer = await did.getSigner();
* const signature = await signer.sign({ data: new TextEncoder().encode('Message') });
* const isValid = await signer.verify({ data: new TextEncoder().encode('Message'), signature });
*
* // Key Management
*
* // Instantiate a DID object from an existing key in a KMS
* const did = await DidJwk.fromKeyManager({
* didUri: 'did:jwk:eyJrIjoiT0tQIiwidCI6IkV1c2UyNTYifQ',
* keyManager
* });
*
* // Instantiate a DID object from an existing verification method key
* const did = await DidJwk.fromKeys({
* verificationMethods: [{
* publicKeyJwk: {
* kty: 'OKP',
* crv: 'Ed25519',
* x: 'cHs7YMLQ3gCWjkacMURBsnEJBcEsvlsE5DfnsfTNDP4'
* },
* privateKeyJwk: {
* kty: 'OKP',
* crv: 'Ed25519',
* x: 'cHs7YMLQ3gCWjkacMURBsnEJBcEsvlsE5DfnsfTNDP4',
* d: 'bdcGE4KzEaekOwoa-ee3gAm1a991WvNj_Eq3WKyqTnE'
* }
* }]
* });
*
* // Convert a DID object to a portable format
* const portableDid = await DidJwk.toKeys({ did });
*
* // Reconstruct a DID object from a portable format
* const did = await DidJwk.fromKeys(portableDid);
* ```
*/
export class DidJwk extends DidMethod {
/**
* Creates a new DID using the `did:jwk` method formed from a newly generated key.
*
* @remarks
* The DID URI is formed by Base64URL-encoding the JWK and prefixing with `did:jwk:`.
*
* Notes:
* - If no `options` are given, by default a new Ed25519 key will be generated.
* - The `algorithm` and `verificationMethods` options are mutually exclusive. If both are given,
* an error will be thrown.
*
* @example
* ```ts
* // DID Creation
* const did = await DidJwk.create();
*
* // DID Creation with a KMS
* const keyManager = new LocalKeyManager();
* const did = await DidJwk.create({ keyManager });
* ```
*
* @param params - The parameters for the create operation.
* @param params.keyManager - Optionally specify a Key Management System (KMS) used to generate
* keys and sign data.
* @param params.options - Optional parameters that can be specified when creating a new DID.
* @returns A Promise resolving to a {@link BearerDid} object representing the new DID.
*/
static create() {
return __awaiter(this, arguments, void 0, function* ({ keyManager = new LocalKeyManager(), options = {} } = {}) {
// Before processing the create operation, validate DID-method-specific requirements to prevent
// keys from being generated unnecessarily.
var _a, _b, _c, _d;
// Check 1: Validate that `algorithm` or `verificationMethods` options are not both given.
if (options.algorithm && options.verificationMethods) {
throw new Error(`The 'algorithm' and 'verificationMethods' options are mutually exclusive`);
}
// Check 2: If `verificationMethods` is given, it must contain exactly one entry since DID JWK
// only supports a single verification method.
if (options.verificationMethods && options.verificationMethods.length !== 1) {
throw new Error(`The 'verificationMethods' option must contain exactly one entry`);
}
// Default to Ed25519 key generation if an algorithm is not given.
const algorithm = (_d = (_a = options.algorithm) !== null && _a !== void 0 ? _a : (_c = (_b = options.verificationMethods) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.algorithm) !== null && _d !== void 0 ? _d : 'Ed25519';
// Generate a new key using the specified `algorithm`.
const keyUri = yield keyManager.generateKey({ algorithm });
const publicKey = yield keyManager.getPublicKey({ keyUri });
// Compute the DID identifier from the public key by serializing the JWK to a UTF-8 string and
// encoding in Base64URL format.
const identifier = Convert.object(publicKey).toBase64Url();
// Attach the prefix `did:jwk` to form the complete DID URI.
const didUri = `did:${DidJwk.methodName}:${identifier}`;
// Expand the DID URI string to a DID document.
const didResolutionResult = yield DidJwk.resolve(didUri);
const document = didResolutionResult.didDocument;
// Create the BearerDid object from the generated key material.
const did = new BearerDid({
uri: didUri,
document,
metadata: {},
keyManager
});
return did;
});
}
/**
* Given the W3C DID Document of a `did:jwk` DID, return the verification method that will be used
* for signing messages and credentials. If given, the `methodId` parameter is used to select the
* verification method. If not given, the first verification method in the DID Document is used.
*
* Note that for DID JWK, only one verification method can exist so specifying `methodId` could be
* considered redundant or unnecessary. The option is provided for consistency with other DID
* method implementations.
*
* @param params - The parameters for the `getSigningMethod` operation.
* @param params.didDocument - DID Document to get the verification method from.
* @param params.methodId - ID of the verification method to use for signing.
* @returns Verification method to use for signing.
*/
static getSigningMethod(_a) {
return __awaiter(this, arguments, void 0, function* ({ didDocument }) {
var _b;
// Verify the DID method is supported.
const parsedDid = Did.parse(didDocument.id);
if (parsedDid && parsedDid.method !== this.methodName) {
throw new DidError(DidErrorCode.MethodNotSupported, `Method not supported: ${parsedDid.method}`);
}
// Attempt to find the verification method in the DID Document.
const [verificationMethod] = (_b = didDocument.verificationMethod) !== null && _b !== void 0 ? _b : [];
if (!(verificationMethod && verificationMethod.publicKeyJwk)) {
throw new DidError(DidErrorCode.InternalError, 'A verification method intended for signing could not be determined from the DID Document');
}
return verificationMethod;
});
}
/**
* Instantiates a {@link BearerDid} object for the DID JWK method from a given {@link PortableDid}.
*
* This method allows for the creation of a `BearerDid` object using a previously created DID's
* key material, DID document, and metadata.
*
* @remarks
* The `verificationMethod` array of the DID document must contain exactly one key since the
* `did:jwk` method only supports a single verification method.
*
* @example
* ```ts
* // Export an existing BearerDid to PortableDid format.
* const portableDid = await did.export();
* // Reconstruct a BearerDid object from the PortableDid.
* const did = await DidJwk.import({ portableDid });
* ```
*
* @param params - The parameters for the import operation.
* @param params.portableDid - The PortableDid object to import.
* @param params.keyManager - Optionally specify an external Key Management System (KMS) used to
* generate keys and sign data. If not given, a new
* {@link LocalKeyManager} instance will be created and
* used.
* @returns A Promise resolving to a `BearerDid` object representing the DID formed from the provided keys.
* @throws An error if the DID document does not contain exactly one verification method.
*/
static import(_a) {
return __awaiter(this, arguments, void 0, function* ({ portableDid, keyManager = new LocalKeyManager() }) {
// Verify the DID method is supported.
const parsedDid = Did.parse(portableDid.uri);
if ((parsedDid === null || parsedDid === void 0 ? void 0 : parsedDid.method) !== DidJwk.methodName) {
throw new DidError(DidErrorCode.MethodNotSupported, `Method not supported`);
}
// Use the given PortableDid to construct the BearerDid object.
const did = yield BearerDid.import({ portableDid, keyManager });
// Validate that the given DID document contains exactly one verification method.
// Note: The non-undefined assertion is necessary because the type system cannot infer that
// the `verificationMethod` property is defined -- which is checked by `BearerDid.import()`.
if (did.document.verificationMethod.length !== 1) {
throw new DidError(DidErrorCode.InvalidDidDocument, `DID document must contain exactly one verification method`);
}
return did;
});
}
/**
* Resolves a `did:jwk` identifier to a DID Document.
*
* @param didUri - The DID to be resolved.
* @param _options - Optional parameters for resolving the DID. Unused by this DID method.
* @returns A Promise resolving to a {@link DidResolutionResult} object representing the result of the resolution.
*/
static resolve(didUri, _options) {
return __awaiter(this, void 0, void 0, function* () {
// Attempt to parse the DID URI.
const parsedDid = Did.parse(didUri);
// Attempt to decode the Base64URL-encoded JWK.
let publicKey;
try {
publicKey = Convert.base64Url(parsedDid.id).toObject();
}
catch ( /* Consume the error so that a DID resolution error can be returned later. */_a) { /* Consume the error so that a DID resolution error can be returned later. */ }
// If parsing or decoding failed, the DID is invalid.
if (!parsedDid || !publicKey) {
return Object.assign(Object.assign({}, EMPTY_DID_RESOLUTION_RESULT), { didResolutionMetadata: { error: 'invalidDid' } });
}
// If the DID method is not "jwk", return an error.
if (parsedDid.method !== DidJwk.methodName) {
return Object.assign(Object.assign({}, EMPTY_DID_RESOLUTION_RESULT), { didResolutionMetadata: { error: 'methodNotSupported' } });
}
const didDocument = {
'@context': [
'https://www.w3.org/ns/did/v1'
],
id: parsedDid.uri
};
const keyUri = `${didDocument.id}#0`;
// Set the Verification Method property.
didDocument.verificationMethod = [{
id: keyUri,
type: 'JsonWebKey',
controller: didDocument.id,
publicKeyJwk: publicKey
}];
// Set the Verification Relationship properties.
didDocument.authentication = [keyUri];
didDocument.assertionMethod = [keyUri];
didDocument.capabilityInvocation = [keyUri];
didDocument.capabilityDelegation = [keyUri];
didDocument.keyAgreement = [keyUri];
// If the JWK contains a `use` property with the value "sig" then the `keyAgreement` property
// is not included in the DID Document. If the `use` value is "enc" then only the `keyAgreement`
// property is included in the DID Document.
switch (publicKey.use) {
case 'sig': {
delete didDocument.keyAgreement;
break;
}
case 'enc': {
delete didDocument.authentication;
delete didDocument.assertionMethod;
delete didDocument.capabilityInvocation;
delete didDocument.capabilityDelegation;
break;
}
}
return Object.assign(Object.assign({}, EMPTY_DID_RESOLUTION_RESULT), { didDocument });
});
}
}
/**
* Name of the DID method, as defined in the DID JWK specification.
*/
DidJwk.methodName = 'jwk';
//# sourceMappingURL=did-jwk.js.map
@@ -0,0 +1 @@
{"version":3,"file":"did-jwk.js","sourceRoot":"","sources":["../../../src/methods/did-jwk.ts"],"names":[],"mappings":";;;;;;;;;AAUA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAM/C,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAC;AAoEzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuEG;AACH,MAAM,OAAO,MAAO,SAAQ,SAAS;IAOnC;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACI,MAAM,CAAO,MAAM;6DAAiD,EACzE,UAAU,GAAG,IAAI,eAAe,EAAE,EAClC,OAAO,GAAG,EAAE,KAIV,EAAE;YACJ,+FAA+F;YAC/F,2CAA2C;;YAE3C,0FAA0F;YAC1F,IAAI,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;gBACrD,MAAM,IAAI,KAAK,CAAC,0EAA0E,CAAC,CAAC;YAC9F,CAAC;YAED,8FAA8F;YAC9F,8CAA8C;YAC9C,IAAI,OAAO,CAAC,mBAAmB,IAAI,OAAO,CAAC,mBAAmB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC5E,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAC;YACrF,CAAC;YAED,kEAAkE;YAClE,MAAM,SAAS,GAAG,MAAA,MAAA,OAAO,CAAC,SAAS,mCAAI,MAAA,MAAA,OAAO,CAAC,mBAAmB,0CAAG,CAAC,CAAC,0CAAE,SAAS,mCAAI,SAAS,CAAC;YAEhG,sDAAsD;YACtD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,WAAW,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC;YAC3D,MAAM,SAAS,GAAG,MAAM,UAAU,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;YAE5D,8FAA8F;YAC9F,gCAAgC;YAChC,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC;YAE3D,4DAA4D;YAC5D,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,UAAU,IAAI,UAAU,EAAE,CAAC;YAExD,+CAA+C;YAC/C,MAAM,mBAAmB,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YACzD,MAAM,QAAQ,GAAG,mBAAmB,CAAC,WAA0B,CAAC;YAEhE,+DAA+D;YAC/D,MAAM,GAAG,GAAG,IAAI,SAAS,CAAC;gBACxB,GAAG,EAAQ,MAAM;gBACjB,QAAQ;gBACR,QAAQ,EAAG,EAAE;gBACb,UAAU;aACX,CAAC,CAAC;YAEH,OAAO,GAAG,CAAC;QACb,CAAC;KAAA;IAED;;;;;;;;;;;;;OAaG;IACI,MAAM,CAAO,gBAAgB;6DAAC,EAAE,WAAW,EAGjD;;YACC,sCAAsC;YACtC,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;YAC5C,IAAI,SAAS,IAAI,SAAS,CAAC,MAAM,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;gBACtD,MAAM,IAAI,QAAQ,CAAC,YAAY,CAAC,kBAAkB,EAAE,yBAAyB,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;YACnG,CAAC;YAED,+DAA+D;YAC/D,MAAM,CAAE,kBAAkB,CAAE,GAAG,MAAA,WAAW,CAAC,kBAAkB,mCAAI,EAAE,CAAC;YAEpE,IAAI,CAAC,CAAC,kBAAkB,IAAI,kBAAkB,CAAC,YAAY,CAAC,EAAE,CAAC;gBAC7D,MAAM,IAAI,QAAQ,CAAC,YAAY,CAAC,aAAa,EAAE,0FAA0F,CAAC,CAAC;YAC7I,CAAC;YAED,OAAO,kBAAkB,CAAC;QAC5B,CAAC;KAAA;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACI,MAAM,CAAO,MAAM;6DAAC,EAAE,WAAW,EAAE,UAAU,GAAG,IAAI,eAAe,EAAE,EAG3E;YACC,sCAAsC;YACtC,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;YAC7C,IAAI,CAAA,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,MAAM,MAAK,MAAM,CAAC,UAAU,EAAE,CAAC;gBAC5C,MAAM,IAAI,QAAQ,CAAC,YAAY,CAAC,kBAAkB,EAAE,sBAAsB,CAAC,CAAC;YAC9E,CAAC;YAED,+DAA+D;YAC/D,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,CAAC;YAEhE,iFAAiF;YACjF,2FAA2F;YAC3F,4FAA4F;YAC5F,IAAI,GAAG,CAAC,QAAQ,CAAC,kBAAmB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAClD,MAAM,IAAI,QAAQ,CAAC,YAAY,CAAC,kBAAkB,EAAE,2DAA2D,CAAC,CAAC;YACnH,CAAC;YAED,OAAO,GAAG,CAAC;QACb,CAAC;KAAA;IAED;;;;;;OAMG;IACI,MAAM,CAAO,OAAO,CAAC,MAAc,EAAE,QAA+B;;YACzE,gCAAgC;YAChC,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAEpC,+CAA+C;YAC/C,IAAI,SAA0B,CAAC;YAC/B,IAAI,CAAC;gBACH,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,SAAU,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAS,CAAC;YACjE,CAAC;YAAC,QAAQ,6EAA6E,IAA/E,CAAC,CAAC,6EAA6E,CAAC,CAAC;YAEzF,qDAAqD;YACrD,IAAI,CAAC,SAAS,IAAI,CAAC,SAAS,EAAE,CAAC;gBAC7B,uCACK,2BAA2B,KAC9B,qBAAqB,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,IAC9C;YACJ,CAAC;YAED,mDAAmD;YACnD,IAAI,SAAS,CAAC,MAAM,KAAK,MAAM,CAAC,UAAU,EAAE,CAAC;gBAC3C,uCACK,2BAA2B,KAC9B,qBAAqB,EAAE,EAAE,KAAK,EAAE,oBAAoB,EAAE,IACtD;YACJ,CAAC;YAED,MAAM,WAAW,GAAgB;gBAC/B,UAAU,EAAE;oBACV,8BAA8B;iBAC/B;gBACD,EAAE,EAAE,SAAS,CAAC,GAAG;aAClB,CAAC;YAEF,MAAM,MAAM,GAAG,GAAG,WAAW,CAAC,EAAE,IAAI,CAAC;YAErC,wCAAwC;YACxC,WAAW,CAAC,kBAAkB,GAAG,CAAC;oBAChC,EAAE,EAAa,MAAM;oBACrB,IAAI,EAAW,YAAY;oBAC3B,UAAU,EAAK,WAAW,CAAC,EAAE;oBAC7B,YAAY,EAAG,SAAS;iBACzB,CAAC,CAAC;YAEH,gDAAgD;YAChD,WAAW,CAAC,cAAc,GAAG,CAAC,MAAM,CAAC,CAAC;YACtC,WAAW,CAAC,eAAe,GAAG,CAAC,MAAM,CAAC,CAAC;YACvC,WAAW,CAAC,oBAAoB,GAAG,CAAC,MAAM,CAAC,CAAC;YAC5C,WAAW,CAAC,oBAAoB,GAAG,CAAC,MAAM,CAAC,CAAC;YAC5C,WAAW,CAAC,YAAY,GAAG,CAAC,MAAM,CAAC,CAAC;YAEpC,6FAA6F;YAC7F,gGAAgG;YAChG,4CAA4C;YAC5C,QAAQ,SAAS,CAAC,GAAG,EAAE,CAAC;gBACtB,KAAK,KAAK,CAAC,CAAC,CAAC;oBACX,OAAO,WAAW,CAAC,YAAY,CAAC;oBAChC,MAAM;gBACR,CAAC;gBAED,KAAK,KAAK,CAAC,CAAC,CAAC;oBACX,OAAO,WAAW,CAAC,cAAc,CAAC;oBAClC,OAAO,WAAW,CAAC,eAAe,CAAC;oBACnC,OAAO,WAAW,CAAC,oBAAoB,CAAC;oBACxC,OAAO,WAAW,CAAC,oBAAoB,CAAC;oBACxC,MAAM;gBACR,CAAC;YACH,CAAC;YAED,uCACK,2BAA2B,KAC9B,WAAW,IACX;QACJ,CAAC;KAAA;;AArPD;;GAEG;AACW,iBAAU,GAAG,KAAK,CAAC"}
+983
View File
@@ -0,0 +1,983 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { Multicodec, universalTypeOf } from '@web5/common';
import { X25519, Ed25519, Secp256k1, Secp256r1, LocalKeyManager, } from '@web5/crypto';
import { Did } from '../did.js';
import { DidMethod } from './did-method.js';
import { BearerDid } from '../bearer-did.js';
import { DidError, DidErrorCode } from '../did-error.js';
import { EMPTY_DID_RESOLUTION_RESULT } from '../types/did-resolution.js';
import { getVerificationMethodTypes, keyBytesToMultibaseId, multibaseIdToKeyBytes } from '../utils.js';
/**
* Enumerates the types of keys that can be used in a DID Key document.
*
* The DID Key method supports various cryptographic key types. These key types are essential for
* the creation and management of DIDs and their associated cryptographic operations like signing
* and encryption.
*/
export var DidKeyRegisteredKeyType;
(function (DidKeyRegisteredKeyType) {
/**
* Ed25519: A public-key signature system using the EdDSA (Edwards-curve Digital Signature
* Algorithm) and Curve25519.
*/
DidKeyRegisteredKeyType["Ed25519"] = "Ed25519";
/**
* secp256k1: A cryptographic curve used for digital signatures in a range of decentralized
* systems.
*/
DidKeyRegisteredKeyType["secp256k1"] = "secp256k1";
/**
* secp256r1: Also known as P-256 or prime256v1, this curve is used for cryptographic operations
* and is widely supported in various cryptographic libraries and standards.
*/
DidKeyRegisteredKeyType["secp256r1"] = "secp256r1";
/**
* X25519: A Diffie-Hellman key exchange algorithm using Curve25519.
*/
DidKeyRegisteredKeyType["X25519"] = "X25519";
})(DidKeyRegisteredKeyType || (DidKeyRegisteredKeyType = {}));
/**
* Enumerates the verification method types supported by the DID Key method.
*
* This enum defines the URIs associated with common verification methods used in DID Documents.
* These URIs represent cryptographic suites or key types standardized for use across decentralized
* identifiers (DIDs).
*/
export const DidKeyVerificationMethodType = {
/** Represents an Ed25519 public key used for digital signatures. */
Ed25519VerificationKey2020: 'https://w3id.org/security/suites/ed25519-2020/v1',
/** Represents a JSON Web Key (JWK) used for digital signatures and key agreement protocols. */
JsonWebKey2020: 'https://w3id.org/security/suites/jws-2020/v1',
/** Represents an X25519 public key used for key agreement protocols. */
X25519KeyAgreementKey2020: 'https://w3id.org/security/suites/x25519-2020/v1',
};
/**
* Private helper that maps algorithm identifiers to their corresponding DID Key
* {@link DidKeyRegisteredKeyType | registered key type}.
*/
const AlgorithmToKeyTypeMap = {
Ed25519: DidKeyRegisteredKeyType.Ed25519,
ES256K: DidKeyRegisteredKeyType.secp256k1,
ES256: DidKeyRegisteredKeyType.secp256r1,
'P-256': DidKeyRegisteredKeyType.secp256r1,
secp256k1: DidKeyRegisteredKeyType.secp256k1,
secp256r1: DidKeyRegisteredKeyType.secp256r1,
X25519: DidKeyRegisteredKeyType.X25519
};
/**
* The `DidKey` class provides an implementation of the 'did:key' DID method.
*
* Features:
* - DID Creation: Create new `did:key` DIDs.
* - DID Key Management: Instantiate a DID object from an existing verification method key set or
* or a key in a Key Management System (KMS). If supported by the KMS, a DID's
* key can be exported to a portable DID format.
* - DID Resolution: Resolve a `did:key` to its corresponding DID Document.
* - Signature Operations: Sign and verify messages using keys associated with a DID.
*
* @remarks
* The `did:key` DID method uses a single public key to generate a DID and does not rely
* on any external system such as a blockchain or centralized database. This characteristic makes
* it suitable for use cases where a assertions about a DID Subject can be self-verifiable by
* third parties.
*
* The method-specific identifier is formed by
* {@link https://datatracker.ietf.org/doc/html/draft-multiformats-multibase#name-base-58-bitcoin-encoding | Multibase base58-btc}
* encoding the concatenation of the
* {@link https://github.com/multiformats/multicodec/blob/master/README.md | Multicodec} identifier
* for the public key type and the raw public key bytes. To form the DID URI, the method-specific
* identifier is prefixed with the string 'did:key:'.
*
* This method can optionally derive an encryption key from the public key used to create the DID
* if and only if the public key algorithm is `Ed25519`. This feature enables the same DID to be
* used for encrypted communication, in addition to signature verification. To enable this
* feature when calling {@link DidKey.create | `DidKey.create()`}, first specify an `algorithm` of
* `Ed25519` or provide a `keySet` referencing an `Ed25519` key and then set the
* `enableEncryptionKeyDerivation` option to `true`.
*
* Note:
* - The authors of the DID Key specification have indicated that use of this method for long-lived
* use cases is only recommended when accompanied with high confidence that private keys are
* securely protected by software or hardware isolation.
*
* @see {@link https://w3c-ccg.github.io/did-method-key/ | DID Key Specification}
*
* @example
* ```ts
* // DID Creation
* const did = await DidKey.create();
*
* // DID Creation with a KMS
* const keyManager = new LocalKeyManager();
* const did = await DidKey.create({ keyManager });
*
* // DID Resolution
* const resolutionResult = await DidKey.resolve({ did: did.uri });
*
* // Signature Operations
* const signer = await did.getSigner();
* const signature = await signer.sign({ data: new TextEncoder().encode('Message') });
* const isValid = await signer.verify({ data: new TextEncoder().encode('Message'), signature });
*
* // Key Management
*
* // Instantiate a DID object from an existing key in a KMS
* const did = await DidKey.fromKeyManager({
* didUri: 'did:key:z6MkpUzNmYVTGpqhStxK8yRKXWCRNm1bGYz8geAg2zmjYHKX',
* keyManager
* });
*
* // Instantiate a DID object from an existing verification method key
* const did = await DidKey.fromKeys({
* verificationMethods: [{
* publicKeyJwk: {
* kty: 'OKP',
* crv: 'Ed25519',
* x: 'cHs7YMLQ3gCWjkacMURBsnEJBcEsvlsE5DfnsfTNDP4'
* },
* privateKeyJwk: {
* kty: 'OKP',
* crv: 'Ed25519',
* x: 'cHs7YMLQ3gCWjkacMURBsnEJBcEsvlsE5DfnsfTNDP4',
* d: 'bdcGE4KzEaekOwoa-ee3gAm1a991WvNj_Eq3WKyqTnE'
* }
* }]
* });
*
* // Convert a DID object to a portable format
* const portableDid = await DidKey.toKeys({ did });
*
* // Reconstruct a DID object from a portable format
* const did = await DidKey.fromKeys(portableDid);
* ```
*/
export class DidKey extends DidMethod {
/**
* Creates a new DID using the `did:key` method formed from a newly generated key.
*
* @remarks
* The DID URI is formed by
* {@link https://datatracker.ietf.org/doc/html/draft-multiformats-multibase#name-base-58-bitcoin-encoding | Multibase base58-btc}
* encoding the
* {@link https://github.com/multiformats/multicodec/blob/master/README.md | Multicodec}-encoded
* public key and prefixing with `did:key:`.
*
* This method can optionally derive an encryption key from the public key used to create the DID
* if and only if the public key algorithm is `Ed25519`. This feature enables the same DID to be
* used for encrypted communication, in addition to signature verification. To enable this
* feature, specify an `algorithm` of `Ed25519` as either a top-level option or in a
* `verificationMethod` and set the `enableEncryptionKeyDerivation` option to `true`.
*
* Notes:
* - If no `options` are given, by default a new Ed25519 key will be generated.
* - The `algorithm` and `verificationMethods` options are mutually exclusive. If both are given,
* an error will be thrown.
*
* @example
* ```ts
* // DID Creation
* const did = await DidKey.create();
*
* // DID Creation with a KMS
* const keyManager = new LocalKeyManager();
* const did = await DidKey.create({ keyManager });
* ```
*
* @param params - The parameters for the create operation.
* @param params.keyManager - Key Management System (KMS) used to generate keys and sign data.
* @param params.options - Optional parameters that can be specified when creating a new DID.
* @returns A Promise resolving to a {@link BearerDid} object representing the new DID.
*/
static create() {
return __awaiter(this, arguments, void 0, function* ({ keyManager = new LocalKeyManager(), options = {} } = {}) {
// Before processing the create operation, validate DID-method-specific requirements to prevent
// keys from being generated unnecessarily.
var _a, _b, _c, _d;
// Check 1: Validate that `algorithm` or `verificationMethods` options are not both given.
if (options.algorithm && options.verificationMethods) {
throw new Error(`The 'algorithm' and 'verificationMethods' options are mutually exclusive`);
}
// Check 2: If `verificationMethods` is given, it must contain exactly one entry since DID Key
// only supports a single verification method.
if (options.verificationMethods && options.verificationMethods.length !== 1) {
throw new Error(`The 'verificationMethods' option must contain exactly one entry`);
}
// Default to Ed25519 key generation if an algorithm is not given.
const algorithm = (_d = (_a = options.algorithm) !== null && _a !== void 0 ? _a : (_c = (_b = options.verificationMethods) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.algorithm) !== null && _d !== void 0 ? _d : 'Ed25519';
// Generate a new key using the specified `algorithm`.
const keyUri = yield keyManager.generateKey({ algorithm });
const publicKey = yield keyManager.getPublicKey({ keyUri });
// Compute the DID identifier from the public key by converting the JWK to a multibase-encoded
// multicodec value.
const identifier = yield DidKeyUtils.publicKeyToMultibaseId({ publicKey });
// Attach the prefix `did:key` to form the complete DID URI.
const didUri = `did:${DidKey.methodName}:${identifier}`;
// Expand the DID URI string to a DID document.
const didResolutionResult = yield DidKey.resolve(didUri, options);
const document = didResolutionResult.didDocument;
// Create the BearerDid object from the generated key material.
const did = new BearerDid({
uri: didUri,
document,
metadata: {},
keyManager
});
return did;
});
}
/**
* Given the W3C DID Document of a `did:key` DID, return the verification method that will be used
* for signing messages and credentials. With DID Key, the first verification method in the
* authentication property in the DID Document is used.
*
* Note that for DID Key, only one verification method intended for signing can exist so
* specifying `methodId` could be considered redundant or unnecessary. The option is provided for
* consistency with other DID method implementations.
*
* @param params - The parameters for the `getSigningMethod` operation.
* @param params.didDocument - DID Document to get the verification method from.
* @param params.methodId - ID of the verification method to use for signing.
* @returns Verification method to use for signing.
*/
static getSigningMethod(_a) {
return __awaiter(this, arguments, void 0, function* ({ didDocument }) {
var _b;
// Verify the DID method is supported.
const parsedDid = Did.parse(didDocument.id);
if (parsedDid && parsedDid.method !== this.methodName) {
throw new DidError(DidErrorCode.MethodNotSupported, `Method not supported: ${parsedDid.method}`);
}
// Attempt to ge the first verification method intended for signing claims.
const [methodId] = didDocument.assertionMethod || [];
const verificationMethod = (_b = didDocument.verificationMethod) === null || _b === void 0 ? void 0 : _b.find(vm => vm.id === methodId);
if (!(verificationMethod && verificationMethod.publicKeyJwk)) {
throw new DidError(DidErrorCode.InternalError, 'A verification method intended for signing could not be determined from the DID Document');
}
return verificationMethod;
});
}
/**
* Instantiates a {@link BearerDid} object for the DID Key method from a given {@link PortableDid}.
*
* This method allows for the creation of a `BearerDid` object using a previously created DID's
* key material, DID document, and metadata.
*
* @remarks
* The `verificationMethod` array of the DID document must contain exactly one key since the
* `did:key` method only supports a single verification method.
*
* @example
* ```ts
* // Export an existing BearerDid to PortableDid format.
* const portableDid = await did.export();
* // Reconstruct a BearerDid object from the PortableDid.
* const did = await DidKey.import({ portableDid });
* ```
*
* @param params - The parameters for the import operation.
* @param params.portableDid - The PortableDid object to import.
* @param params.keyManager - Optionally specify an external Key Management System (KMS) used to
* generate keys and sign data. If not given, a new
* {@link LocalKeyManager} instance will be created and
* used.
* @returns A Promise resolving to a `BearerDid` object representing the DID formed from the provided keys.
* @throws An error if the DID document does not contain exactly one verification method.
*/
static import(_a) {
return __awaiter(this, arguments, void 0, function* ({ portableDid, keyManager = new LocalKeyManager() }) {
// Verify the DID method is supported.
const parsedDid = Did.parse(portableDid.uri);
if ((parsedDid === null || parsedDid === void 0 ? void 0 : parsedDid.method) !== DidKey.methodName) {
throw new DidError(DidErrorCode.MethodNotSupported, `Method not supported`);
}
// Use the given PortableDid to construct the BearerDid object.
const did = yield BearerDid.import({ portableDid, keyManager });
// Validate that the given DID document contains exactly one verification method.
// Note: The non-undefined assertion is necessary because the type system cannot infer that
// the `verificationMethod` property is defined -- which is checked by `BearerDid.import()`.
if (did.document.verificationMethod.length !== 1) {
throw new DidError(DidErrorCode.InvalidDidDocument, `DID document must contain exactly one verification method`);
}
return did;
});
}
/**
* Resolves a `did:key` identifier to a DID Document.
*
* @param didUri - The DID to be resolved.
* @param options - Optional parameters for resolving the DID.
* @returns A Promise resolving to a {@link DidResolutionResult} object representing the result of the resolution.
*/
static resolve(didUri, options) {
return __awaiter(this, void 0, void 0, function* () {
try {
// Attempt to expand the DID URI string to a DID document.
const didDocument = yield DidKey.createDocument({ didUri, options });
// If the DID document was created successfully, return it.
return Object.assign(Object.assign({}, EMPTY_DID_RESOLUTION_RESULT), { didDocument });
}
catch (error) {
// Rethrow any unexpected errors that are not a `DidError`.
if (!(error instanceof DidError))
throw new Error(error);
// Return a DID Resolution Result with the appropriate error code.
return Object.assign(Object.assign({}, EMPTY_DID_RESOLUTION_RESULT), { didResolutionMetadata: Object.assign({ error: error.code }, error.message && { errorMessage: error.message }) });
}
});
}
/**
* Expands a did:key identifier to a DID Document.
*
* Reference: https://w3c-ccg.github.io/did-method-key/#document-creation-algorithm
*
* @param options
* @returns - A DID dodcument.
*/
static createDocument(_a) {
return __awaiter(this, arguments, void 0, function* ({ didUri, options = {} }) {
const { defaultContext = 'https://www.w3.org/ns/did/v1', enableEncryptionKeyDerivation = false, enableExperimentalPublicKeyTypes = false, publicKeyFormat = 'JsonWebKey2020' } = options;
/**
* 1. Initialize document to an empty object.
*/
const didDocument = { id: '' };
/**
* 2. Using a colon (:) as the delimiter, split the identifier into its
* components: a scheme, a method, a version, and a multibaseValue.
* If there are only three components set the version to the string
* value 1 and use the last value as the multibaseValue.
*/
const parsedDid = Did.parse(didUri);
if (!parsedDid) {
throw new DidError(DidErrorCode.InvalidDid, `Invalid DID URI: ${didUri}`);
}
const multibaseValue = parsedDid.id;
/**
* 3. Check the validity of the input identifier.
* The scheme MUST be the value did. The method MUST be the value key.
* The version MUST be convertible to a positive integer value. The
* multibaseValue MUST be a string and begin with the letter z. If any
* of these requirements fail, an invalidDid error MUST be raised.
*/
if (parsedDid.method !== DidKey.methodName) {
throw new DidError(DidErrorCode.MethodNotSupported, `Method not supported: ${parsedDid.method}`);
}
if (!DidKey.validateIdentifier(parsedDid)) {
throw new DidError(DidErrorCode.InvalidDid, `Invalid DID URI: ${didUri}`);
}
/**
* 4. Initialize the signatureVerificationMethod to the result of passing
* identifier, multibaseValue, and options to a
* {@link https://w3c-ccg.github.io/did-method-key/#signature-method-creation-algorithm | Signature Method Creation Algorithm}.
*/
const signatureVerificationMethod = yield DidKey.createSignatureMethod({
didUri,
multibaseValue,
options: { enableExperimentalPublicKeyTypes, publicKeyFormat }
});
/**
* 5. Set document.id to identifier. If document.id is not a valid DID,
* an invalidDid error MUST be raised.
*
* Note: Identifier was already confirmed to be valid in Step 3, so
* skipping the redundant validation.
*/
didDocument.id = parsedDid.uri;
/**
* 6. Initialize the verificationMethod property in document to an array
* where the first value is the signatureVerificationMethod.
*/
didDocument.verificationMethod = [signatureVerificationMethod];
/**
* 7. Initialize the authentication, assertionMethod, capabilityInvocation,
* and the capabilityDelegation properties in document to an array where
* the first item is the value of the id property in
* signatureVerificationMethod.
*/
didDocument.authentication = [signatureVerificationMethod.id];
didDocument.assertionMethod = [signatureVerificationMethod.id];
didDocument.capabilityInvocation = [signatureVerificationMethod.id];
didDocument.capabilityDelegation = [signatureVerificationMethod.id];
/**
* 8. If options.enableEncryptionKeyDerivation is set to true:
* Add the encryptionVerificationMethod value to the verificationMethod
* array. Initialize the keyAgreement property in document to an array
* where the first item is the value of the id property in
* encryptionVerificationMethod.
*/
if (enableEncryptionKeyDerivation === true) {
/**
* Although not covered by the did:key method specification, a sensible
* default will be taken to use the 'X25519KeyAgreementKey2020'
* verification method type if the given publicKeyFormat is
* 'Ed25519VerificationKey2020' and 'JsonWebKey2020' otherwise.
*/
const encryptionPublicKeyFormat = (publicKeyFormat === 'Ed25519VerificationKey2020')
? 'X25519KeyAgreementKey2020'
: 'JsonWebKey2020';
/**
* 8.1 Initialize the encryptionVerificationMethod to the result of
* passing identifier, multibaseValue, and options to an
* {@link https://w3c-ccg.github.io/did-method-key/#encryption-method-creation-algorithm | Encryption Method Creation Algorithm}.
*/
const encryptionVerificationMethod = yield this.createEncryptionMethod({
didUri,
multibaseValue,
options: { enableExperimentalPublicKeyTypes, publicKeyFormat: encryptionPublicKeyFormat }
});
/**
* 8.2 Add the encryptionVerificationMethod value to the
* verificationMethod array.
*/
didDocument.verificationMethod.push(encryptionVerificationMethod);
/**
* 8.3. Initialize the keyAgreement property in document to an array
* where the first item is the value of the id property in
* encryptionVerificationMethod.
*/
didDocument.keyAgreement = [encryptionVerificationMethod.id];
}
/**
* 9. Initialize the @context property in document to the result of passing document and options to the Context
* Creation algorithm.
*/
// Set contextArray to an array that is initialized to options.defaultContext.
const contextArray = [defaultContext];
// For every object in every verification relationship listed in document,
// add a string value to the contextArray based on the object type value,
// if it doesn't already exist, according to the following table:
// {@link https://w3c-ccg.github.io/did-method-key/#context-creation-algorithm | Context Type URL}
const verificationMethodTypes = getVerificationMethodTypes({ didDocument });
verificationMethodTypes.forEach((typeName) => {
const typeUrl = DidKeyVerificationMethodType[typeName];
contextArray.push(typeUrl);
});
didDocument['@context'] = contextArray;
/**
* 10. Return document.
*/
return didDocument;
});
}
/**
* Decoding a multibase-encoded multicodec value into a verification method
* that is suitable for verifying that encrypted information will be
* received by the intended recipient.
*/
static createEncryptionMethod(_a) {
return __awaiter(this, arguments, void 0, function* ({ didUri, multibaseValue, options }) {
const { enableExperimentalPublicKeyTypes, publicKeyFormat } = options;
/**
* 1. Initialize verificationMethod to an empty object.
*/
const verificationMethod = { id: '', type: '', controller: '' };
/**
* 2. Set multicodecValue and raw publicKeyBytes to the result of passing multibaseValue and
* options to a Derive Encryption Key algorithm.
*/
const { keyBytes: publicKeyBytes, multicodecCode: multicodecValue, } = yield DidKey.deriveEncryptionKey({ multibaseValue });
/**
* 3. Ensure the proper key length of raw publicKeyBytes based on the multicodecValue table
* provided below:
*
* Multicodec hexadecimal value: 0xec
*
* If the byte length of raw publicKeyBytes does not match the expected public key length for
* the associated multicodecValue, an invalidPublicKeyLength error MUST be raised.
*/
const actualLength = publicKeyBytes.byteLength;
const expectedLength = DidKeyUtils.MULTICODEC_PUBLIC_KEY_LENGTH[multicodecValue];
if (actualLength !== expectedLength) {
throw new DidError(DidErrorCode.InvalidPublicKeyLength, `Expected ${actualLength} bytes. Actual: ${expectedLength}`);
}
/**
* 4. Create the multibaseValue by concatenating the letter 'z' and the
* base58-btc encoding of the concatenation of the multicodecValue and
* the raw publicKeyBytes.
*/
const kemMultibaseValue = keyBytesToMultibaseId({
keyBytes: publicKeyBytes,
multicodecCode: multicodecValue
});
/**
* 5. Set the verificationMethod.id value by concatenating identifier,
* a hash character (#), and the multibaseValue. If verificationMethod.id
* is not a valid DID URL, an invalidDidUrl error MUST be raised.
*/
verificationMethod.id = `${didUri}#${kemMultibaseValue}`;
try {
new URL(verificationMethod.id);
}
catch (error) {
throw new DidError(DidErrorCode.InvalidDidUrl, 'Verification Method ID is not a valid DID URL.');
}
/**
* 6. Set the publicKeyFormat value to the options.publicKeyFormat value.
* 7. If publicKeyFormat is not known to the implementation, an
* unsupportedPublicKeyType error MUST be raised.
*/
if (!(publicKeyFormat in DidKeyVerificationMethodType)) {
throw new DidError(DidErrorCode.UnsupportedPublicKeyType, `Unsupported format: ${publicKeyFormat}`);
}
/**
* 8. If options.enableExperimentalPublicKeyTypes is set to false and publicKeyFormat is not
* Multikey, JsonWebKey2020, or X25519KeyAgreementKey2020, an invalidPublicKeyType error MUST be
* raised.
*/
const StandardPublicKeyTypes = ['Multikey', 'JsonWebKey2020', 'X25519KeyAgreementKey2020'];
if (enableExperimentalPublicKeyTypes === false
&& !(StandardPublicKeyTypes.includes(publicKeyFormat))) {
throw new DidError(DidErrorCode.InvalidPublicKeyType, `Specified '${publicKeyFormat}' without setting enableExperimentalPublicKeyTypes to true.`);
}
/**
* 9. Set verificationMethod.type to the publicKeyFormat value.
*/
verificationMethod.type = publicKeyFormat;
/**
* 10. Set verificationMethod.controller to the identifier value.
*/
verificationMethod.controller = didUri;
/**
* 11. If publicKeyFormat is Multikey or X25519KeyAgreementKey2020, set the verificationMethod.publicKeyMultibase
* value to multibaseValue.
*
* Note: This implementation does not currently support the Multikey
* format.
*/
if (publicKeyFormat === 'X25519KeyAgreementKey2020') {
verificationMethod.publicKeyMultibase = kemMultibaseValue;
}
/**
* 12. If publicKeyFormat is JsonWebKey2020, set the verificationMethod.publicKeyJwk value to
* the result of passing multicodecValue and rawPublicKeyBytes to a JWK encoding algorithm.
*/
if (publicKeyFormat === 'JsonWebKey2020') {
const { crv } = yield DidKeyUtils.multicodecToJwk({ code: multicodecValue });
verificationMethod.publicKeyJwk = yield DidKeyUtils.keyConverter(crv).bytesToPublicKey({ publicKeyBytes });
}
/**
* 13. Return verificationMethod.
*/
return verificationMethod;
});
}
/**
* Decodes a multibase-encoded multicodec value into a verification method
* that is suitable for verifying digital signatures.
* @param options - Signature method creation algorithm inputs.
* @returns - A verification method.
*/
static createSignatureMethod(_a) {
return __awaiter(this, arguments, void 0, function* ({ didUri, multibaseValue, options }) {
const { enableExperimentalPublicKeyTypes, publicKeyFormat } = options;
/**
* 1. Initialize verificationMethod to an empty object.
*/
const verificationMethod = { id: '', type: '', controller: '' };
/**
* 2. Set multicodecValue and publicKeyBytes to the result of passing
* multibaseValue and options to a Decode Public Key algorithm.
*/
const { keyBytes: publicKeyBytes, multicodecCode: multicodecValue, multicodecName } = multibaseIdToKeyBytes({ multibaseKeyId: multibaseValue });
/**
* 3. Ensure the proper key length of publicKeyBytes based on the multicodecValue
* {@link https://w3c-ccg.github.io/did-method-key/#signature-method-creation-algorithm | table provided}.
* If the byte length of rawPublicKeyBytes does not match the expected public key length for the
* associated multicodecValue, an invalidPublicKeyLength error MUST be raised.
*/
const actualLength = publicKeyBytes.byteLength;
const expectedLength = DidKeyUtils.MULTICODEC_PUBLIC_KEY_LENGTH[multicodecValue];
if (actualLength !== expectedLength) {
throw new DidError(DidErrorCode.InvalidPublicKeyLength, `Expected ${actualLength} bytes. Actual: ${expectedLength}`);
}
/**
* 4. Ensure the publicKeyBytes are a proper encoding of the public key type as specified by
* the multicodecValue. If an invalid public key value is detected, an invalidPublicKey error
* MUST be raised.
*/
let isValid = false;
switch (multicodecName) {
case 'secp256k1-pub':
isValid = yield Secp256k1.validatePublicKey({ publicKeyBytes });
break;
case 'ed25519-pub':
isValid = yield Ed25519.validatePublicKey({ publicKeyBytes });
break;
case 'x25519-pub':
// TODO: Validate key once/if X25519.validatePublicKey() is implemented.
// isValid = X25519.validatePublicKey({ key: rawPublicKeyBytes})
isValid = true;
break;
}
if (!isValid) {
throw new DidError(DidErrorCode.InvalidPublicKey, 'Invalid public key detected.');
}
/**
* 5. Set the verificationMethod.id value by concatenating identifier, a hash character (#), and
* the multibaseValue. If verificationMethod.id is not a valid DID URL, an invalidDidUrl error
* MUST be raised.
*/
verificationMethod.id = `${didUri}#${multibaseValue}`;
try {
new URL(verificationMethod.id);
}
catch (error) {
throw new DidError(DidErrorCode.InvalidDidUrl, 'Verification Method ID is not a valid DID URL.');
}
/**
* 6. Set the publicKeyFormat value to the options.publicKeyFormat value.
* 7. If publicKeyFormat is not known to the implementation, an unsupportedPublicKeyType error
* MUST be raised.
*/
if (!(publicKeyFormat in DidKeyVerificationMethodType)) {
throw new DidError(DidErrorCode.UnsupportedPublicKeyType, `Unsupported format: ${publicKeyFormat}`);
}
/**
* 8. If options.enableExperimentalPublicKeyTypes is set to false and publicKeyFormat is not
* Multikey, JsonWebKey2020, or Ed25519VerificationKey2020, an invalidPublicKeyType error MUST
* be raised.
*/
const StandardPublicKeyTypes = ['Multikey', 'JsonWebKey2020', 'Ed25519VerificationKey2020'];
if (enableExperimentalPublicKeyTypes === false
&& !(StandardPublicKeyTypes.includes(publicKeyFormat))) {
throw new DidError(DidErrorCode.InvalidPublicKeyType, `Specified '${publicKeyFormat}' without setting enableExperimentalPublicKeyTypes to true.`);
}
/**
* 9. Set verificationMethod.type to the publicKeyFormat value.
*/
verificationMethod.type = publicKeyFormat;
/**
* 10. Set verificationMethod.controller to the identifier value.
*/
verificationMethod.controller = didUri;
/**
* 11. If publicKeyFormat is Multikey or Ed25519VerificationKey2020,
* set the verificationMethod.publicKeyMultibase value to multibaseValue.
*
* Note: This implementation does not currently support the Multikey
* format.
*/
if (publicKeyFormat === 'Ed25519VerificationKey2020') {
verificationMethod.publicKeyMultibase = multibaseValue;
}
/**
* 12. If publicKeyFormat is JsonWebKey2020, set the verificationMethod.publicKeyJwk value to
* the result of passing multicodecValue and rawPublicKeyBytes to a JWK encoding algorithm.
*/
if (publicKeyFormat === 'JsonWebKey2020') {
const { crv } = yield DidKeyUtils.multicodecToJwk({ code: multicodecValue });
verificationMethod.publicKeyJwk = yield DidKeyUtils.keyConverter(crv).bytesToPublicKey({ publicKeyBytes });
}
/**
* 13. Return verificationMethod.
*/
return verificationMethod;
});
}
/**
* Transform a multibase-encoded multicodec value to public encryption key
* components that are suitable for encrypting messages to a receiver. A
* mathematical proof elaborating on the safety of performing this operation
* is available in:
* {@link https://eprint.iacr.org/2021/509.pdf | On using the same key pair for Ed25519 and an X25519 based KEM}
*/
static deriveEncryptionKey(_a) {
return __awaiter(this, arguments, void 0, function* ({ multibaseValue }) {
/**
* 1. Set publicEncryptionKey to an empty object.
*/
let publicEncryptionKey = {
keyBytes: new Uint8Array(),
multicodecCode: 0
};
/**
* 2. Decode multibaseValue using the base58-btc multibase alphabet and
* set multicodecValue to the multicodec header for the decoded value.
* Implementers are cautioned to ensure that the multicodecValue is set
* to the result after performing varint decoding.
*
* 3. Set the rawPublicKeyBytes to the bytes remaining after the multicodec
* header.
*/
const { keyBytes: publicKeyBytes, multicodecCode: multicodecValue } = multibaseIdToKeyBytes({ multibaseKeyId: multibaseValue });
/**
* 4. If the multicodecValue is 0xed (Ed25519 public key), derive a public X25519 encryption key
* by using the raw publicKeyBytes and the algorithm defined in
* {@link https://datatracker.ietf.org/doc/html/draft-ietf-core-oscore-groupcomm | Group OSCORE - Secure Group Communication for CoAP}
* for Curve25519 in Section 2.4.2: ECDH with Montgomery Coordinates and set
* generatedPublicEncryptionKeyBytes to the result.
*/
if (multicodecValue === 0xed) {
const ed25519PublicKey = yield DidKeyUtils.keyConverter('Ed25519').bytesToPublicKey({
publicKeyBytes
});
const generatedPublicEncryptionKey = yield Ed25519.convertPublicKeyToX25519({
publicKey: ed25519PublicKey
});
const generatedPublicEncryptionKeyBytes = yield DidKeyUtils.keyConverter('Ed25519').publicKeyToBytes({
publicKey: generatedPublicEncryptionKey
});
/**
* 5. Set multicodecValue to 0xec.
* 6. Set raw public keyBytes to generatedPublicEncryptionKeyBytes.
*/
publicEncryptionKey = {
keyBytes: generatedPublicEncryptionKeyBytes,
multicodecCode: 0xec
};
}
/**
* 7. Return publicEncryptionKey.
*/
return publicEncryptionKey;
});
}
/**
* Validates the structure and components of a DID URI against the `did:key` method specification.
*
* @param parsedDid - An object representing the parsed components of a DID URI, including the
* scheme, method, and method-specific identifier.
* @returns `true` if the DID URI meets the `did:key` method's structural requirements, `false` otherwise.
*
*/
static validateIdentifier(parsedDid) {
const { method, id: multibaseValue } = parsedDid;
const [scheme] = parsedDid.uri.split(':', 1);
/**
* Note: The W3C DID specification makes no mention of a version value being part of the DID
* syntax. Additionally, there does not appear to be any real-world usage of the version
* number. Consequently, this implementation will ignore the version related guidance in
* the did:key specification.
*/
const version = '1';
return (scheme === 'did' &&
method === 'key' &&
Number(version) > 0 &&
universalTypeOf(multibaseValue) === 'String' &&
multibaseValue.startsWith('z'));
}
}
/**
* Name of the DID method, as defined in the DID Key specification.
*/
DidKey.methodName = 'key';
/**
* The `DidKeyUtils` class provides utility functions to support operations in the DID Key method.
*/
export class DidKeyUtils {
/**
* Converts a JWK (JSON Web Key) to a Multicodec code and name.
*
* @example
* ```ts
* const jwk: Jwk = { crv: 'Ed25519', kty: 'OKP', x: '...' };
* const { code, name } = await DidKeyUtils.jwkToMulticodec({ jwk });
* ```
*
* @param params - The parameters for the conversion.
* @param params.jwk - The JSON Web Key to be converted.
* @returns A promise that resolves to a Multicodec definition.
*/
static jwkToMulticodec(_a) {
return __awaiter(this, arguments, void 0, function* ({ jwk }) {
const params = [];
if (jwk.crv) {
params.push(jwk.crv);
if (jwk.d) {
params.push('private');
}
else {
params.push('public');
}
}
const lookupKey = params.join(':');
const name = DidKeyUtils.JWK_TO_MULTICODEC[lookupKey];
if (name === undefined) {
throw new Error(`Unsupported JWK to Multicodec conversion: '${lookupKey}'`);
}
const code = Multicodec.getCodeFromName({ name });
return { code, name };
});
}
/**
* Returns the appropriate public key compressor for the specified cryptographic curve.
*
* @param curve - The cryptographic curve to use for the key conversion.
* @returns A public key compressor for the specified curve.
*/
static keyCompressor(curve) {
// ): ({ publicKeyBytes }: { publicKeyBytes: Uint8Array }) => Promise<Uint8Array> {
const compressors = {
'P-256': Secp256r1.compressPublicKey,
'secp256k1': Secp256k1.compressPublicKey
};
const compressor = compressors[curve];
if (!compressor)
throw new DidError(DidErrorCode.InvalidPublicKeyType, `Unsupported curve: ${curve}`);
return compressor;
}
/**
* Returns the appropriate key converter for the specified cryptographic curve.
*
* @param curve - The cryptographic curve to use for the key conversion.
* @returns An `AsymmetricKeyConverter` for the specified curve.
*/
static keyConverter(curve) {
const converters = {
'Ed25519': Ed25519,
'P-256': Secp256r1,
'secp256k1': Secp256k1,
'X25519': X25519
};
const converter = converters[curve];
if (!converter)
throw new DidError(DidErrorCode.InvalidPublicKeyType, `Unsupported curve: ${curve}`);
return converter;
}
/**
* Converts a Multicodec code or name to parial JWK (JSON Web Key).
*
* @example
* ```ts
* const partialJwk = await DidKeyUtils.multicodecToJwk({ name: 'ed25519-pub' });
* ```
*
* @param params - The parameters for the conversion.
* @param params.code - Optional Multicodec code to convert.
* @param params.name - Optional Multicodec name to convert.
* @returns A promise that resolves to a JOSE format key.
*/
static multicodecToJwk(_a) {
return __awaiter(this, arguments, void 0, function* ({ code, name }) {
// Either code or name must be specified, but not both.
if (!(name ? !code : code)) {
throw new Error(`Either 'name' or 'code' must be defined, but not both.`);
}
// If name is undefined, lookup by code.
name = (name === undefined) ? Multicodec.getNameFromCode({ code: code }) : name;
const lookupKey = name;
const jose = DidKeyUtils.MULTICODEC_TO_JWK[lookupKey];
if (jose === undefined) {
throw new Error(`Unsupported Multicodec to JWK conversion`);
}
return Object.assign({}, jose);
});
}
/**
* Converts a public key in JWK (JSON Web Key) format to a multibase identifier.
*
* @remarks
* Note: All secp public keys are converted to compressed point encoding
* before the multibase identifier is computed.
*
* Per {@link https://github.com/multiformats/multicodec/blob/master/table.csv | Multicodec table}:
* Public keys for Elliptic Curve cryptography algorithms (e.g., secp256k1,
* secp256k1r1, secp384r1, etc.) are always represented with compressed point
* encoding (e.g., secp256k1-pub, p256-pub, p384-pub, etc.).
*
* Per {@link https://datatracker.ietf.org/doc/html/rfc8812#name-jose-and-cose-secp256k1-cur | RFC 8812}:
* "As a compressed point encoding representation is not defined for JWK
* elliptic curve points, the uncompressed point encoding defined there
* MUST be used. The x and y values represented MUST both be exactly
* 256 bits, with any leading zeros preserved."
*
* @example
* ```ts
* const publicKey = { crv: 'Ed25519', kty: 'OKP', x: '...' };
* const multibaseId = await DidKeyUtils.publicKeyToMultibaseId({ publicKey });
* ```
*
* @param params - The parameters for the conversion.
* @param params.publicKey - The public key in JWK format.
* @returns A promise that resolves to the multibase identifier.
*/
static publicKeyToMultibaseId(_a) {
return __awaiter(this, arguments, void 0, function* ({ publicKey }) {
var _b;
if (!((publicKey === null || publicKey === void 0 ? void 0 : publicKey.crv) && publicKey.crv in AlgorithmToKeyTypeMap)) {
throw new DidError(DidErrorCode.InvalidPublicKeyType, `Public key contains an unsupported key type: ${(_b = publicKey === null || publicKey === void 0 ? void 0 : publicKey.crv) !== null && _b !== void 0 ? _b : 'undefined'}`);
}
// Convert the public key from JWK format to a byte array.
let publicKeyBytes = yield DidKeyUtils.keyConverter(publicKey.crv).publicKeyToBytes({ publicKey });
// Compress the public key if it is an elliptic curve key.
if (/^(secp256k1|P-256|P-384|P-521)$/.test(publicKey.crv)) {
publicKeyBytes = yield DidKeyUtils.keyCompressor(publicKey.crv)({ publicKeyBytes });
}
// Convert the JSON Web Key (JWK) parameters to a Multicodec name.
const { name: multicodecName } = yield DidKeyUtils.jwkToMulticodec({ jwk: publicKey });
// Compute the multibase identifier based on the provided key.
const multibaseId = keyBytesToMultibaseId({
keyBytes: publicKeyBytes,
multicodecName
});
return multibaseId;
});
}
}
/**
* A mapping from JSON Web Key (JWK) property descriptors to multicodec names.
*
* This mapping is used to convert keys in JWK (JSON Web Key) format to multicodec format.
*
* @remarks
* The keys of this object are strings that describe the JOSE key type and usage,
* such as 'Ed25519:public', 'Ed25519:private', etc. The values are the corresponding multicodec
* names used to represent these key types.
*
* @example
* ```ts
* const multicodecName = JWK_TO_MULTICODEC['Ed25519:public'];
* // Returns 'ed25519-pub', the multicodec name for an Ed25519 public key
* ```
*/
DidKeyUtils.JWK_TO_MULTICODEC = {
'Ed25519:public': 'ed25519-pub',
'Ed25519:private': 'ed25519-priv',
'secp256k1:public': 'secp256k1-pub',
'secp256k1:private': 'secp256k1-priv',
'X25519:public': 'x25519-pub',
'X25519:private': 'x25519-priv',
};
/**
* Defines the expected byte lengths for public keys associated with different cryptographic
* algorithms, indexed by their multicodec code values.
*/
DidKeyUtils.MULTICODEC_PUBLIC_KEY_LENGTH = {
// secp256k1-pub - Secp256k1 public key (compressed) - 33 bytes
0xe7: 33,
// x25519-pub - Curve25519 public key - 32 bytes
0xec: 32,
// ed25519-pub - Ed25519 public key - 32 bytes
0xed: 32
};
/**
* A mapping from multicodec names to their corresponding JOSE (JSON Object Signing and Encryption)
* representations. This mapping facilitates the conversion of multicodec key formats to
* JWK (JSON Web Key) formats.
*
* @remarks
* The keys of this object are multicodec names, such as 'ed25519-pub', 'ed25519-priv', etc.
* The values are objects representing the corresponding JWK properties for that key type.
*
* @example
* ```ts
* const joseKey = MULTICODEC_TO_JWK['ed25519-pub'];
* // Returns a partial JWK for an Ed25519 public key
* ```
*/
DidKeyUtils.MULTICODEC_TO_JWK = {
'ed25519-pub': { crv: 'Ed25519', kty: 'OKP', x: '' },
'ed25519-priv': { crv: 'Ed25519', kty: 'OKP', x: '', d: '' },
'secp256k1-pub': { crv: 'secp256k1', kty: 'EC', x: '', y: '' },
'secp256k1-priv': { crv: 'secp256k1', kty: 'EC', x: '', y: '', d: '' },
'x25519-pub': { crv: 'X25519', kty: 'OKP', x: '' },
'x25519-priv': { crv: 'X25519', kty: 'OKP', x: '', d: '' },
};
//# sourceMappingURL=did-key.js.map
File diff suppressed because one or more lines are too long
+53
View File
@@ -0,0 +1,53 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
/**
* Base abstraction for all Decentralized Identifier (DID) method implementations.
*
* This base class serves as a foundational structure upon which specific DID methods
* can be implemented. Subclasses should furnish particular method and data models adherent
* to various DID methods, taking care to adhere to the
* {@link https://www.w3.org/TR/did-core/ | W3C DID Core specification} and the
* respective DID method specifications.
*/
export class DidMethod {
/**
* MUST be implemented by all DID method implementations that extend {@link DidMethod}.
*
* Given the W3C DID Document of a DID, return the verification method that will be used for
* signing messages and credentials. If given, the `methodId` parameter is used to select the
* verification method. If not given, each DID method implementation will select a default
* verification method from the DID Document.
*
* @param _params - The parameters for the `getSigningMethod` operation.
* @param _params.didDocument - DID Document to get the verification method from.
* @param _params.methodId - ID of the verification method to use for signing.
* @returns Verification method to use for signing.
*/
static getSigningMethod(_params) {
return __awaiter(this, void 0, void 0, function* () {
throw new Error(`Not implemented: Classes extending DidMethod must implement getSigningMethod()`);
});
}
/**
* MUST be implemented by all DID method implementations that extend {@link DidMethod}.
*
* Resolves a DID URI to a DID Document.
*
* @param _didUri - The DID to be resolved.
* @param _options - Optional parameters for resolving the DID.
* @returns A Promise resolving to a {@link DidResolutionResult} object representing the result of the resolution.
*/
static resolve(_didUri, _options) {
return __awaiter(this, void 0, void 0, function* () {
throw new Error(`Not implemented: Classes extending DidMethod must implement resolve()`);
});
}
}
//# sourceMappingURL=did-method.js.map
@@ -0,0 +1 @@
{"version":3,"file":"did-method.js","sourceRoot":"","sources":["../../../src/methods/did-method.ts"],"names":[],"mappings":";;;;;;;;;AAyOA;;;;;;;;GAQG;AACH,MAAM,OAAO,SAAS;IACpB;;;;;;;;;;;;OAYG;IACI,MAAM,CAAO,gBAAgB,CAAC,OAGpC;;YACC,MAAM,IAAI,KAAK,CAAC,gFAAgF,CAAC,CAAC;QACpG,CAAC;KAAA;IAED;;;;;;;;OAQG;IACI,MAAM,CAAO,OAAO,CAAC,OAAe,EAAE,QAA+B;;YAC1E,MAAM,IAAI,KAAK,CAAC,uEAAuE,CAAC,CAAC;QAC3F,CAAC;KAAA;CACF"}
+83
View File
@@ -0,0 +1,83 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { Did } from '../did.js';
import { DidMethod } from './did-method.js';
import { EMPTY_DID_RESOLUTION_RESULT } from '../types/did-resolution.js';
/**
* The `DidWeb` class provides an implementation of the `did:web` DID method.
*
* Features:
* - DID Resolution: Resolve a `did:web` to its corresponding DID Document.
*
* @remarks
* The `did:web` method uses a web domain's existing reputation and aims to integrate decentralized
* identities with the existing web infrastructure to drive adoption. It leverages familiar web
* security models and domain ownership to provide accessible, interoperable digital identity
* management.
*
* @see {@link https://w3c-ccg.github.io/did-method-web/ | DID Web Specification}
*
* @example
* ```ts
* // DID Resolution
* const resolutionResult = await DidWeb.resolve({ did: did.uri });
* ```
*/
export class DidWeb extends DidMethod {
/**
* Resolves a `did:web` identifier to a DID Document.
*
* @param didUri - The DID to be resolved.
* @param _options - Optional parameters for resolving the DID. Unused by this DID method.
* @returns A Promise resolving to a {@link DidResolutionResult} object representing the result of the resolution.
*/
static resolve(didUri, _options) {
return __awaiter(this, void 0, void 0, function* () {
// Attempt to parse the DID URI.
const parsedDid = Did.parse(didUri);
// If parsing failed, the DID is invalid.
if (!parsedDid) {
return Object.assign(Object.assign({}, EMPTY_DID_RESOLUTION_RESULT), { didResolutionMetadata: { error: 'invalidDid' } });
}
// If the DID method is not "web", return an error.
if (parsedDid.method !== DidWeb.methodName) {
return Object.assign(Object.assign({}, EMPTY_DID_RESOLUTION_RESULT), { didResolutionMetadata: { error: 'methodNotSupported' } });
}
// Replace ":" with "/" in the identifier and prepend "https://" to obtain the fully qualified
// domain name and optional path.
let baseUrl = `https://${parsedDid.id.replace(/:/g, '/')}`;
// If the domain contains a percent encoded port value, decode the colon.
baseUrl = decodeURIComponent(baseUrl);
// Append the expected location of the DID document depending on whether a path was specified.
const didDocumentUrl = parsedDid.id.includes(':') ?
`${baseUrl}/did.json` :
`${baseUrl}/.well-known/did.json`;
try {
// Perform an HTTP GET request to obtain the DID document.
const response = yield fetch(didDocumentUrl);
// If the response status code is not 200, return an error.
if (!response.ok)
throw new Error('HTTP error status code returned');
// Parse the DID document.
const didDocument = yield response.json();
return Object.assign(Object.assign({}, EMPTY_DID_RESOLUTION_RESULT), { didDocument });
}
catch (error) {
// If the DID document could not be retrieved, return an error.
return Object.assign(Object.assign({}, EMPTY_DID_RESOLUTION_RESULT), { didResolutionMetadata: { error: 'notFound' } });
}
});
}
}
/**
* Name of the DID method, as defined in the DID Web specification.
*/
DidWeb.methodName = 'web';
//# sourceMappingURL=did-web.js.map
@@ -0,0 +1 @@
{"version":3,"file":"did-web.js","sourceRoot":"","sources":["../../../src/methods/did-web.ts"],"names":[],"mappings":";;;;;;;;;AAEA,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAC;AAEzE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,OAAO,MAAO,SAAQ,SAAS;IAOnC;;;;;;OAMG;IACI,MAAM,CAAO,OAAO,CAAC,MAAc,EAAE,QAA+B;;YACzE,gCAAgC;YAChC,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAEpC,yCAAyC;YACzC,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,uCACK,2BAA2B,KAC9B,qBAAqB,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,IAC9C;YACJ,CAAC;YAED,mDAAmD;YACnD,IAAI,SAAS,CAAC,MAAM,KAAK,MAAM,CAAC,UAAU,EAAE,CAAC;gBAC3C,uCACK,2BAA2B,KAC9B,qBAAqB,EAAE,EAAE,KAAK,EAAE,oBAAoB,EAAE,IACtD;YACJ,CAAC;YAED,8FAA8F;YAC9F,iCAAiC;YACjC,IAAI,OAAO,GAAG,WAAW,SAAS,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;YAE3D,yEAAyE;YACzE,OAAO,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;YAEtC,8FAA8F;YAC9F,MAAM,cAAc,GAAG,SAAS,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;gBACjD,GAAG,OAAO,WAAW,CAAC,CAAC;gBACvB,GAAG,OAAO,uBAAuB,CAAC;YAEpC,IAAI,CAAC;gBACH,0DAA0D;gBAC1D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,cAAc,CAAC,CAAC;gBAE7C,2DAA2D;gBAC3D,IAAI,CAAC,QAAQ,CAAC,EAAE;oBAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;gBAErE,0BAA0B;gBAC1B,MAAM,WAAW,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAiB,CAAC;gBAEzD,uCACK,2BAA2B,KAC9B,WAAW,IACX;YAEJ,CAAC;YAAC,OAAO,KAAU,EAAE,CAAC;gBACpB,+DAA+D;gBAC/D,uCACK,2BAA2B,KAC9B,qBAAqB,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,IAC5C;YACJ,CAAC;QACH,CAAC;KAAA;;AAlED;;GAEG;AACW,iBAAU,GAAG,KAAK,CAAC"}