# DID Methods ### DID Manager? ```typescript const dm = new DidManager({ keyManager }); const did = await dm.create({ method: 'dht', options: { ... } }) ``` ### ```DidMethod.create()``` - Only allows creating a new DID object and generating new keys - If called with no parameters (e.g., `DidMethod.create()` a local key manager that stores keys in-memory will be used for key generation using a default algorithm (e.g., `Ed25519`) - The method optionally takes a keyManager which will be an instance of a KMS (could be local key manager with memory/disk persistence, AWS KMS key manager, or a custom implementation) - The method optionally takes `verificationMethods` that specify - The method optionally takes `services` which will be an array of `DidService` objects - Returns a `Did` object or instance (depending on the SDK) that has `uri`, `didDocument`, `keyManager`, and `metadata` properties along with a convenience function getSigner() #### TypeScript Example Types shared by all DID Method implementations: ```typescript export interface DidCreateOptions { /** * Optional. An array of verification methods to be included in the DID document. */ verificationMethods?: DidCreateVerificationMethod[]; } export interface DidCreateVerificationMethod { algorithm: AlgorithmIdentifier; controller?: string; id?: string; purposes?: VerificationRelationship[]; } ``` DID DHT: :::info - If `options` are not specified for `create()`, a DID DHT method implementation will generate a new `Ed25519` identity key and the DID Document will not include any additional verification method or service entries. The identity key must be an `Ed25519` key so there is no reason to provide a configurable option to allow a developer to specify an alternative key algorithm. - Optionally, a developer can choose to add one or more `verificationMethod` entries to the DID Document. - The only required property for adding verification methods is `algorithm` which determines the key generation algorithm (e.g., `Ed25519`, `ES256K`, etc.) the key manager will use. - By default, the `controller` property is set to the DID Subject of the DID Document (`id` property), but this can be optionally overriden. - By default, the verification method will be added to all verification relationship purposes, but this can be optionally overriden. - By default, the verification method's `id` property will be set to the `kid` of the generated key. This can be optionally overridden by specifying an `id` value, which will be appended to `did:dht:method-specific-id#`. - Optionally, a developer can choose to add one or more `service` entries to the DID Document. - For each service, the `id`, `type`, and `serviceEndpoint` properties are required. Additional properties can be included in the `DidService` object. - Optionally, a developer can choose to NOT publish the DID to a DID DHT Gateway by specifying a `publish` value of `false`. By default, newly created DIDs are published. ::: ```typescript export interface DidDhtCreateOptions extends DidCreateOptions { alsoKnownAs: string[]; controllers: string | string[]; publish?: boolean; services?: DidService[]; verificationMethods?: DidCreateVerificationMethod[]; } class DidDht { public static async create({ keyManager, options = {} }: { keyManager?: CryptoApi; options?: DidDhtCreateOptions; }): Promise<Did> { // Implementation } } ``` DID JWK: :::info - If `options` are not specified for `create()`, a DID JWK method implementation will generate a new `Ed25519` key. - Optionally, a developer can choose to specify an `algorithm` which determines the key generation algorithm (e.g., `Ed25519`, `ES256K`, etc.) the key manager will use. - Since a `did:jwk` DID can only represent a single key, there is no reason to provide any options beyond `algorithm`. ::: ```typescript export interface DidJwkCreateOptions extends DidCreateOptions { /** Optionally specify the algorithm to be used for key generation. */ algorithm?: AlgorithmIdentifier; } class DidJwk { public static async create({ keyManager, options = {} }: { keyManager?: CryptoApi; options?: DidJwkCreateOptions; }): Promise<Did> { // Implementation } } ``` --- ### `DidMethod.fromKeys()` - Only allow instantiating a DID using previously generated keys - Keys are provided as a key set which contains one or more keys - returns the same `Did` object as `create()`. #### TypeScript Example Types shared by all DID Method implementations: ```typescript export type DidKeySetVerificationMethod = RequireOnly<DidVerificationMethod, 'privateKeyJwk' | 'publicKeyJwk'> & { purposes?: DidVerificationRelationship[]; } interface DidKeySet { verificationMethods: DidKeySetVerificationMethod[]; } ``` DID JWK: ```typescript const did = await DidJwk.fromKeys({ verificationMethods: [ { publicKeyJwk: { alg: "EdDSA", kty: "OKP", crv: "Ed25519", kid: "....", x: "5zKah1H9z_VJnfMAEoRhUBXlsy0PDF5etSwrTU0TAHE" }, privateKeyJwk: { alg: "EdDSA", kty: "OKP", crv: "Ed25519", kid: "....", d: "kDYH0X2nmF-tq-XqlJy9bqWNi39B92uIvzguX3UwH0w", x: "5zKah1H9z_VJnfMAEoRhUBXlsy0PDF5etSwrTU0TAHE" }, purposes: ['authentication', 'assertionMethod'] } ] }); ``` DID DHT: ```typescript interface DidDhtKeySet extends DidKeySet { identityKey: DidKeySetVerificationMethod[]; } const did = await DidDht.fromKeys({ verificationMethods: [ { publicKeyJwk: { alg: "EdDSA", kty: "OKP", crv: "Ed25519", kid: "....", x: "5zKah1H9z_VJnfMAEoRhUBXlsy0PDF5etSwrTU0TAHE" }, purposes: ['authentication', 'assertionMethod'] } ], identityKey: { publicKeyJwk: { alg: "EdDSA", kty: "OKP", crv: "Ed25519", kid: "....", x: "5zKah1H9z_VJnfMAEoRhUBXlsy0PDF5etSwrTU0TAHE" }, controller?: '...', purpose: ['authentication', 'assertionMethod'] } }); ``` DID Key: This approach would also support methods like DID Key that use other key representation formats: ```typescript const did = await DidKey.fromKeys({ verificationMethods: [ { publicKeyMultibase: "...", purpose: ['authentication', 'assertionMethod'] } ] }); ``` --- ### `DidMethod.fromKeyManager()` - Only allows instantiating a previously created DID using existing keys that are stored in a key manager instance. - The only input is the DID URI and the `keyManager`. - returns the same `Did` object as `create()`. #### TypeScript Example DID JWK: ```typescript const did = await DidJwk.fromKeyManager({ didUri: 'did:jwk:abcd1234', keyManager }); ``` DID DHT: ```typescript const did = await DidDht.fromKeyManager({ didUri: 'did:dht:abcd1234', keyManager }); ``` DID Key: ```typescript const did = await DidDht.fromKeyManager({ didUri: 'did:dht:abcd1234', keyManager }); ``` --- ### `DidMethod.toKeys()` After either `create()` or `fromKeys()` returns a `Did` object/instance, a developer can transform (or "export") the DID back to a key set: DID DHT: ```typescript class DidDht { static toKeySet({ did: Did }) { let keySet: DidDhtKeySet; for (const vm in did.didDocument.verificationMethod) { const keyUri = did.keyManager.getKeyUri({ key: vm.publicKeyJwk }); const privateKeyJwk = did.keyManager.export({ keyUri }); if (vm.id.endsWith('#0')) { keySet.identityKey.push({ ...vm, privateKeyJwk}); } else { keySet.verificationMethods.push({ ...vm, privateKeyJwk}); } } return keySet; } } ``` DID JWK: ```typescript class DidJwk { static toKeySet({ did: Did }) { let keySet: DidKeySet; for (const vm in did.didDocument.verificationMethod) { const keyUri = did.keyManager.getKeyUri({ key: vm.publicKeyJwk }); const privateKeyJwk = did.keyManager.export({ keyUri }); keySet.verificationMethods.push({ ...vm, privateKeyJwk}); } return keySet; } } ``` DID Key: ```typescript class DidKey { static toKeySet({ did: Did }) { let keySet: DidKeySet; for (const vm in did.didDocument.verificationMethod) { const { publicKeyMultibase, ...method } = vm; const publicKeyJwk = vm.publicKeyJwk ?? multibaseToJwk(publicKeyMultibase); const keyUri = did.keyManager.getKeyUri({ key: publicKeyJwk }); const privateKeyJwk = did.keyManager.export({ keyUri }); keySet.verificationMethods.push({ ...method, publicKeyJwk, privateKeyJwk}); } return keySet; } } ``` --- ## Open Questions ### 1. Should we use `purposes` or `relationships` to refer to the list of verification relationships? [DID Spec Language](https://www.w3.org/TR/did-core/#verification-relationships): > A verification relationship expresses the relationship between the DID subject and a verification method. > > Different verification relationships enable the associated verification methods to be used for different purposes. It is up to a verifier to ascertain the validity of a verification attempt by checking that the verification method used is contained in the appropriate verification relationship property of the DID Document. ### 2. What should the name of the from/to methods be? - `DidDht.fromKeySet` / `DidDht.toKeySet` - `DidDht.fromKeys` / `DidDht.toKeys` - something else? ### 3. Are we good with the `create()` is only for creating NEW DIDs and `fromKeys()` is only for instantiating a `Did` from an existing set of keys? ### 4. Should we return the private and public keys in `toKeySet()` or just public keys? If we eliminated returning private keys, that simplifies the logic needed to handle AWS KMS since it can't export private keys. The downside is that it then requires a small amount of additional work for a developer if the KMS being used supports private key export (e.g., default local, in-memory KMS) and they want to get the DID's private keys. It's not that complex, however: ```typescript // Example if `toKeySet()` does NOT return private keys. const keySet = await DidDht.toKeySet({ did }); for (const key in keySet) { const keyUri = did.keyManager.getKeyUri({ key: key.publicKeyJwk }); const privateKeyJwk = did.keyManager.export({ keyUri }); // Do whatever they need to do with private keys... } ``` ### 5. If we do export private keys, how do we handle `toKeySet()` for AWS KMS? Since AWS KMS can't export private keys, one option is for the DID Method implementation to attempt to export the keys, and if that results in an Error, then return the key set with just the public key material. The benefit to this approach is that the DID Methods will work regardless of whether key export is supported by the underlying KMS AND the returned key set could still be used with `DidMethod.fromKeys()` to instantiate a `Did` object. For example: ```typescript class DidJwk { static toKeySet({ did: Did }) { let keySet: DidKeySet; for (const vm in did.didDocument.verificationMethod) { let privateKeyJwk: Jwk | undefined; try { const keyUri = did.keyManager.getKeyUri({ key: vm.publicKeyJwk }); const privateKeyJwk = did.keyManager.export({ keyUri }); keySet.verificationMethods.push({ ...vm, privateKeyJwk}); } catch { keySet.verificationMethods.push({ ...vm}); } } return keySet; } } ```