## Abstract
The did:prism DID method is build on top of the Cardano blockchain.
The method allows to create, update, deactivate and resolve DIDs.
The basic architecture of the method can be explained as follows:
- Operations (Create, Update, Deactivate) are published on the blockchain using transaction's metadata.
+ A single transaction can allocate many operations grouped in AtalaBlocks (see models below).
- The method allows to optionally create DIDs without any interaction with the blockchain.
+ In a similar way to did:key and many DID methods, the user can generate the initial state of its intended DID document and encode it in the DID suffix itself. This leads to a DID of the form:
did:prism:<initial_state_hash>:<encoded_initial_state>
- For DID document updates, the initial state (Create operation) must be published on the blockchain.
+ Note that the user can publish in the same transaction both the Create and Update operations to save in fees.
+ The user then pushes an update operation declaring keys and services to be added and removed from the DID document.
- Deactivation is also published on-chain and also assumes the initial state to be already published.
- The method defines a ProtocolUpdate operation that defines when a new version of the protocol (DID method) is activated.
- In order to resolve DIDs, applications (a.k.a. PRISM nodes) must read the blockchain and extract the operations from metadata.
+ The PRISM nodes will read the operations, run validation rules and construct a mapping from DIDs to their corresponding associated keys and services.
+ Resolvers can take the data returned by PRISM nodes and build compliant DID documents from it.
In the next section we describe in depth operations and validation rules.
## Protocol operations definitions
PRISM operations are defined in protobuf models.
Below we show the definitions related to the protocol operations and their validation rules.
### Top level models
As mentioned above, we have 4 operations posted on-chain in this DID method.
```kotlin
// The possible operations affecting the blockchain.
message AtalaOperation {
// The actual operation.
oneof operation {
// Used to create a public DID.
CreateDIDOperation create_did = 1;
// Used to update an existing public DID.
UpdateDIDOperation update_did = 2;
// Used to deactivate DID
DeactivateDIDOperation deactivate_did = 6;
// Used to announce new protocol update
ProtocolVersionUpdateOperation protocol_version_update = 5;
// NOT RELATED TO THE DID METHOD DEFINITION
// Used to issue a batch of credentials.
IssueCredentialBatchOperation issue_credential_batch = 3;
// Used to revoke a credential batch.
RevokeCredentialsOperation revoke_credentials = 4;
};
}
```
This operations are wrapped in a model that contains signatures and key ids for later validation.
```kotlin
// A signed operation, necessary to post anything on the blockchain.
message SignedAtalaOperation {
// The key ID used to sign the operation, it must belong to the DID that signs the operation.
string signed_with = 1;
bytes signature = 2; // The actual signature.
AtalaOperation operation = 3; // The operation that was signed.
}
```
Operations are posted inside blocks
```kotlin
message AtalaBlock {
reserved 1; // Represents the version of the block. Deprecated
// A signed operation, necessary to post anything on the blockchain.
repeated SignedAtalaOperation operations = 2;
}
```
and blocks are wrapped in Objects which are the models published on-chain
```kotlin
/**
* Wraps an AtalaBlock and its metadata.
*/
message AtalaObject {
reserved 1; // Removed block_hash field.
reserved "block_hash";
int32 block_operation_count = 2; // Number of operations in the block.
int32 block_byte_length = 3; // Byte length of the block.
AtalaBlock block_content = 4; // The block content.
}
```
The existence of AtalaObjects is intended for future versions of the protocol, where we might publish reference to blocks instead of the blocks themselves.
### CreateDID
Now we can move towards the first operation, CreateDID.
```kotlin
message CreateDIDOperation {
DIDCreationData did_data = 1; // DIDCreationData with public keys
// The data necessary to create a DID.
message DIDCreationData {
reserved 1; // Removed DID id field which is empty on creation
repeated PublicKey public_keys = 2; // The keys that belong to this DID Document.
repeated Service services = 3; // The list of service endpoints that belong to this DID Document.
}
}
```
- As we can see, we allow users to create any configuration of keys and services for their DID associated initial state.
- The only requirement is for the user to add at least one MASTER_KEY (see types below).
- All operations related to a DID must be signed by a key of type MASTER_KEY associated to the DID at the time of validating the operation.
- For CreateDID, any master key present in the DIDCreationData object of did_data is considered valid.
Below, we can see some complementary protobuf definitions.
```kotlin
message ECKeyData {
string curve = 1; // The curve name, like secp256k1.
bytes x = 2; // The x coordinate, represented as bytes.
bytes y = 3; // The y coordinate, represented as bytes.
}
message CompressedECKeyData {
string curve = 1; // The curve name, like secp256k1.
bytes data = 2; // compressed Elliptic Curve (EC) public key data.
}
enum KeyUsage {
UNKNOWN_KEY = 0; // protobuf default
MASTER_KEY = 1; // used to update DID documents
ISSUING_KEY = 2; // used to sign VCs and anchor VC data on-chain
COMMUNICATION_KEY = 3; // could likely be mapped to key_exchange type
AUTHENTICATION_KEY = 4;
REVOCATION_KEY = 5; // used to revoke VCs on-chain
}
message PublicKey {
reserved 3, 4;
string id = 1; // The key identifier within the DID Document.
KeyUsage usage = 2; // The key's purpose.
LedgerData added_on = 5; // The ledger details related to the event that added the key to the DID Document.
LedgerData revoked_on = 6; // The ledger details related to the event that revoked the key to the DID Document.
// The key's representation.
oneof key_data {
ECKeyData ec_key_data = 8; // The Elliptic Curve (EC) key.
CompressedECKeyData compressed_ec_key_data = 9; // Compressed Elliptic Curve (EC) key.
};
}
message Service {
string id = 1;
Type type = 2;
enum Type {
UNKNOWN_TYPE = 0; // default protobuf value
// some known services we need for DID comm
}
string service_endpoint = 3;
LedgerData added_on = 4; // (when present) The ledger details related to the event that added the key to the DID Document.
LedgerData revoked_on = 5; // (when present) The ledger details related to the event that revoked the key to the DID Document.
}
```
The associated DID suffix will be the hex encoded hash of the AtalaOperation containing the CreateDIDOperation. Note that this model does not contain a signature. The resulting DID is hence.
`did:prism:<hex_encoded_atala_operation>`
When the user decides to not publish the CreateDID operation on-chain, then the DID becomes
`did:prism:<hex_encoded_atala_operation>:<base64URL_atala_operation>`
We refer to the later as "unpublished" DID or "long-form" DID, while the former case is the "short form" of the DID.
When a PRISM node fetches a new CreateDID operation from the blockchain, it checks the signature of the SignedAtalaOperation, and run additional validations on the key types, ids and services. If validations are
successful, the node stores the new DID in its database and associates it to the seen keys and services.
### UpdateDID
- The next operation a user can perform after creation is UpdateDID.
- An update operation refers to the last update (or CreateDID if this is the first update) through the hash of the last AtalaOperation applied to the DID.
- The UpdateDID operation also contains the DID suffix in the id field that refers to which DID the update is expected.
- Finally we have a sequence of actions to be performed on the current state of the DID document,
```kotlin
// Specifies the necessary data to update a public DID.
message UpdateDIDOperation {
bytes previous_operation_hash = 1; // The hash of the operation that issued the DID.
string id = 2; // @exclude TODO: To be redefined after we start using this operation.
repeated UpdateDIDAction actions = 3; // The actual updates to perform on the DID.
}
```
The 4 possible actions are:
```kotlin
// The potential details that can be updated in a DID.
message UpdateDIDAction {
// The action to perform.
oneof action {
AddKeyAction add_key = 1; // Used to add a new key to the DID.
RemoveKeyAction remove_key = 2; // Used to remove a key from the DID.
AddServiceAction add_service = 3; // Used to add a new service to the DID.
RemoveServiceAction remove_service = 4; // Used to remove a service from the DID.
}
}
```
and are defined as follows:
```kotlin
// The necessary data to add a key to a DID.
message AddKeyAction {
PublicKey key = 1; // The key to include.
}
// The necessary data to remove a key from a DID.
message RemoveKeyAction {
string keyId = 1; // the key id to remove
}
// The necessary data to add a service to a DID document.
message AddServiceAction {
Service service = 1; // The service to include.
}
// The necessary data to remove a service from a DID Document.
message RemoveServiceAction {
string service_id = 1; // the id to of the service to remove
}
```
Note that this structure means that each key can have only one verification relationship using the same key id.
As expected, when a node sees a signed UpdateDID operation, it will run similar validations as with CreateDID and update the DID information with new keys/services, and mark the needed ones as "revoked"
### DeactivateDID
The third operation that we can perform on DIDs is deactivation.
```kotlin
message DeactivateDIDOperation {
bytes previous_operation_hash = 1; // The hash of the operation that issued the DID.
string id = 2; // DID Suffix of the DID to be deactivated
}
```
As expected, this operation will revoke all keys and endpoints associated to a DID, making its active information empty, and making it immutable, due to the removal of all keys of type MASTER_KEY.
## Questions
1. We have legacy DIDs that only contain a single MASTER_KEY. Should we translate the master key type to an AUTHENTICATION verification relationship?
2. We only allow one verification relationship per key id, do we want to make this more flexible?
3. Should we add more verification relationships?
4. Do we care about versioning and network namespace at the level of protobuf operations?
5. Do we want to support backwards compatibility with v1.4 DIDs? And what will the engineering effort be to support backwards compatibility?
6. What service types do we need to support? Note that the DID core registry looks sort of unstable/empty https://www.w3.org/TR/did-spec-registries/#service-types
7. Should we allow a service to update its type and service endpoints' list?
8. ASAP define requirements for the DID Method to feed into engineering team?