TBD54566975 / web5-spec

https://tbd54566975.github.io/sdk-report-runner/
Apache License 2.0
6 stars 5 forks source link

DID creation / update / deactivate reponse #112

Open frankhinek opened 5 months ago

frankhinek commented 5 months ago

Context

The W3C DID Core specification contains guidance for the data structure to be returned by the resolve and dereference functions:

Additionally, there draft community report that a new W3C WG is being started to finalize:

No such guidance exists for other DID registration operations such as create, update, and deactivate. The closest is a draft DIF spec that one of the W3C DID Core editors started:

Current State of SDKs

At present, the shape of result returned by create implementations vary widely by SDK:

It is expected and a positive that each SDK takes a slightly different approach to implementation due to idiomatic differences. However, it would be beneficial to have some general consistency on what is returned when a new DID is created, and for those DID methods that support it, updated or deactivated.


Proposal for Did object returned by create() or update()

/**
 * Represents a Decentralized Identifier (DID) along with its convenience functions.
 */
export interface Did {
  /**
   * The DID document associated with this DID.
   */
  didDocument: DidDocument;

  /**
   * Returns a Signer that can be used to sign messages, credentials, or arbitrary data.
   *
   * If given, the `keyUri` parameter is used to select a key from the verification methods present
   * in the DID Document. If `keyUri` is not given, each DID method implementation will select a
   * default verification method key from the DID Document.
   *
   * @param params - The parameters for the `getSigner` operation.
   * @param params.keyUri - Key URI of the key that will be used for sign and verify operations. Optional.
   * @returns An instantiated Signer that can be used to sign and verify data.
   */
  getSigner: (params?: { keyUri?: string }) => Promise<Signer>;

  /**
   * Key Management System (KMS) used to manage a DIDs keys and sign data.
   *
   * Each DID method requires at least one key be present in the provided `keyManager`.
   */
  keyManager: KeyManager;

  /**
   * Represents metadata about a DID resulting from create, update, or deactivate operations.
   */
  metadata: DidMetadata;

  /**
   * A string representation of the DID.
   *
   * A DID is a URI composed of three parts: the scheme `did:`, a method identifier, and a unique,
   * method-specific identifier specified by the DID method.
   *
   * @example
   * did:dht:h4d3ixkwt6q5a455tucw7j14jmqyghdtbr6cpiz6on5oxj5bpr3o
   */
  uri: string;
}

/**
 * Represents metadata about a DID resulting from create, update, or deactivate operations.
 */
export type DidMetadata = {
  // Additional properties of any type.
  [key: string]: any;
}

/**
 * Format to document a DID identifier, along with its associated data, which can be exported,
 * saved to a file, or imported. The intent is bundle all of the necessary metadata to enable usage
 * of the DID in different contexts.
 */
/**
 * Format that documents the key material and metadata of a Decentralized Identifier (DID) to enable
 * usage of the DID in different contexts.
 *
 * This format is useful for exporting, saving to a file, or importing a DID across process
 * boundaries or between different DID method implementations.
 *
 * @example
 * ```ts
 * // Generate a new DID.
 * const did = await DidExample.create();
 *
 * // Export the DID to a PortableDid.
 * const portableDid = await DidExample.toKeys({ did });
 *
 * // Instantiate a `Did` object from the PortableDid metadata.
 * const didFromKeys = await DidExample.fromKeys({ ...portableDid });
 * // The `didFromKeys` object should be equivalent to the original `did` object.
 * ```
 */
export interface PortableDid {
  /** {@inheritDoc DidUri#uri} */
  uri?: string;

  /**
   * An array of verification methods, including the key material and key purpose, which are
   * included in the DID document.
   *
   * @see {@link https://www.w3.org/TR/did-core/#verification-methods | DID Core Specification, § Verification Methods}
   */
  verificationMethods: PortableDidVerificationMethod[];
}

Proposal for DidMethod.create()

Pseudo type definition for simple methods like DID JWK:

export interface DidCreateOptions {
  /**
   * Optional. An array of verification methods to be included in the DID document.
   */
  verificationMethods?: DidCreateVerificationMethod[];
}

/**
 * Options for additional verification methods added to the DID Document during the creation of a
 * new Decentralized Identifier (DID).
 */
export interface DidCreateVerificationMethod {
  /**
   * The name of the cryptographic algorithm to be used for key generation.
   *
   * Examples might include `Ed25519` and `ES256K` but will vary depending on the DID method
   * specification and the key management system in use.
   */
  algorithm: string;

  /**
   * Optionally specify the purposes for which a verification method is intended to be used in a DID
   * document.
   *
   * The `purposes` property defines the specific
   * {@link DidVerificationRelationship | verification relationships} between the DID subject and
   * the verification method. This enables the verification method to be utilized for distinct
   * actions such as authentication, assertion, key agreement, capability delegation, and others. It
   * is important for verifiers to recognize that a verification method must be associated with the
   * relevant purpose in the DID document to be valid for that specific use case.
   */
  purposes?: (DidVerificationRelationship | keyof typeof DidVerificationRelationship)[];
}

Type definition for a more complex method like DID DHT:

export interface DidDhtCreateOptions extends DidCreateOptions {
  /**
   * Optionally specify that the DID Subject is also identified by one or more other DIDs or URIs.
   *
   * A DID subject can have multiple identifiers for different purposes, or at different times.
   * The assertion that two or more DIDs (or other types of URI) refer to the same DID subject can
   * be made using the `alsoKnownAs` property.
   *
   * @see {@link https://www.w3.org/TR/did-core/#also-known-as | DID Core Specification, § Also Known As}
   *
   * @example
   * ```ts
   * const did = await DidDht.create({
   *  options: {
   *   alsoKnownAs: 'did:example:123'
   * };
   * ```
   */
  alsoKnownAs?: string[];

  /**
   * Optionally specify which DID (or DIDs) is authorized to make changes to the DID document.
   *
   * A DID controller is an entity that is authorized to make changes to a DID document. Typically,
   * only the DID Subject (i.e., the value of `id` property in the DID document) is authoritative.
   * However, another DID (or DIDs) can be specified as the DID controller, and when doing so, any
   * verification methods contained in the DID document for the other DID should be accepted as
   * authoritative. In other words, proofs created by the controller DID should be considered
   * equivalent to proofs created by the DID Subject.
   *
   * @see {@link https://www.w3.org/TR/did-core/#did-controller | DID Core Specification, § DID Controller}
   *
   * @example
   * ```ts
   * const did = await DidDht.create({
   *  options: {
   *   controller: 'did:example:123'
   * };
   * ```
   */
  controllers?: string | string[];

  /**
   * Optional. The URI of a server involved in executing DID method operations. In the context of
   * DID creation, the endpoint is expected to be a Pkarr relay. If not specified, a default gateway
   * node is used.
   */
  gatewayUri?: string;

  /**
   * Optional. Determines whether the created DID should be published to the DHT network.
   *
   * If set to `true` or omitted, the DID is publicly discoverable. If `false`, the DID is not
   * published and cannot be resolved by others. By default, newly created DIDs are published.
   *
   * @see {@link https://did-dht.com | DID DHT Method Specification}
   *
   * @example
   * ```ts
   * const did = await DidDht.create({
   *  options: {
   *   publish: false
   * };
   * ```
   */
  publish?: boolean;

  /**
   * Optional. An array of service endpoints associated with the DID.
   *
   * Services are used in DID documents to express ways of communicating with the DID subject or
   * associated entities. A service can be any type of service the DID subject wants to advertise,
   * including decentralized identity management services for further discovery, authentication,
   * authorization, or interaction.
   *
   * @see {@link https://www.w3.org/TR/did-core/#services | DID Core Specification, § Services}
   *
   * @example
   * ```ts
   * const did = await DidDht.create({
   *  options: {
   *   services: [
   *     {
   *       id: 'did:dht:i9xkp8ddcbcg8jwq54ox699wuzxyifsqx4jru45zodqu453ksz6y#dwn',
   *       type: 'DecentralizedWebNode',
   *       serviceEndpoint: ['https://example.com/dwn1', 'https://example/dwn2']
   *     }
   *   ]
   * };
   * ```
   */
  services?: DidService[];

  /**
   * Optionally specify one or more registered DID DHT types to make the DID discovereable.
   *
   * Type indexing is an OPTIONAL feature that enables DIDs to become discoverable. DIDs that wish
   * to be discoverable and resolveable by type can include one or more types when publishing their
   * DID document to a DID DHT Gateway.
   *
   * The registered DID types are published in the {@link https://did-dht.com/registry/index.html#indexed-types | DID DHT Registry}.
   */
  types?: (DidDhtRegisteredDidType | keyof typeof DidDhtRegisteredDidType)[];

  /**
   * Optional. An array of verification methods to be included in the DID document.
   *
   * By default, a newly created DID DHT document will contain a single Ed25519 verification method,
   * also known as the {@link https://did-dht.com/#term:identity-key | Identity Key}. Additional
   * verification methods can be added to the DID document using the `verificationMethods` property.
   *
   * @see {@link https://www.w3.org/TR/did-core/#verification-methods | DID Core Specification, § Verification Methods}
   *
   * @example
   * ```ts
   * const did = await DidDht.create({
   *  options: {
   *   verificationMethods: [
   *     {
   *       algorithm: 'Ed25519',
   *       purposes: ['authentication', 'assertionMethod']
   *     },
   *     {
   *       algorithm: 'Ed25519',
   *       id: 'dwn-sig',
   *       purposes: ['authentication', 'assertionMethod']
   *     }
   *   ]
   * };
   * ```
   */
  verificationMethods?: DidCreateVerificationMethod<TKms>[];
}

DidMethod.fromKeys()


DidMethod.fromKeyManager()


DidMethod.toKeys()

After either create() or fromKeys() returns a Did object/instance, a developer can transform (or "export") the DID back to a PortableDid.

frankhinek commented 5 months ago

Output from the call today:

BearerDid

{
    uri: "did:method:123",
    document: {}, // DID Document
    metadata: {
        published: boolean
        // method specific properties
    },

    // Instance of `KeyManager` that holds keys for the DID
    keyManager,

    // Returns a Signer with `sign()` and `verify()` methods and `algorithm` and `keyId` properties
    getSigner: (params?: { methodId?: string }) => Signer;

    // Returns a `PortableDid` with the properties described above.
    export: () => PortableDid;
}

PortableDid

{
    uri: "did:method:123",
    document: {}, // DID Document
    metadata: {
        published: boolean
        // method specific properties
    }

    // Optional and would be empty if `AwsKeyManager` since keys can't be exported
    privateKeys: [{...jwk}]
}

Additional Changes

mistermoe commented 5 months ago

Any thoughts on calling fromPortableDid and toPortableDid importBearerDid and export respectively?

Thought it might make sense given that it is a port able did! No biggie if not. Just staring at did.BearerDIDFromPortableDID and/or did<method>.BearerDIDFromPortableDID and thinking "damn.. that's a long function name"

frankhinek commented 5 months ago

While they are verbose, I do appreciate the symmetry of fromPortableDid() and toPortableDid() to make it clear that these methods are involved in converting back and forth between PortableDid.

Something about importBearerDid and export makes it less clear that the two complement each other. It also exposes the developer to having to understand what a BearerDid is right away, which is also true of toPortableDid()/fromPortableDid().

IMHO importBearerDid reads as "importing a BearerDid" -- but then a developer must provide a PortableDid as input, which might be slightly confusing for some.

If you do want to change, what about:

These two methods enable the round-trip conversion of DID types, ensuring that DID material & metadata can be transformed between runtime and portable formats, thereby enabling interoperability between the various Web5 SDKs.

If the method is DidJwk.import() and did.export() it ought to be pretty obvious what's being exported and imported. If we wanted to be a bit more verbose we could make that DidJwk.importDid() and did.exportDid() but not sure if thats necessary.

All that being said...

It seems regardless of the language that it should be did<method>.fromPortableDID() or did<method>.import() rather than trying to attach BearerDIDFromPortableDID() to a generic BearerDID or DID.