├── .github ├── CODEOWNERS ├── issue_template.md └── pull_request_template.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── extensions └── did-prism-anoncreds-object-method-specification.md └── w3c-spec └── PRISM-method.md /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # These owners will be the default owners for everything in 2 | # the repo. Unless a later match takes precedence 3 | * @EzequielPostan 4 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Different DID methods exhibit distinct characteristics and involve inherent design trade-offs. For example, some methods prioritize instant DID creation but lack mechanisms for subsequent updates, while others prioritize high throughput of DID events but sacrifice features like DID exchange or historical auditability. 2 | 3 | When choosing a DID method that aligns with specific use cases, it is crucial to thoroughly evaluate the associated trade-offs. It is hard to expect any single DID method to cater to all possible scenarios, and the PRISM DID method is no exception. Therefore, we list the principles and trade-offs of our DID method in the next section. 4 | 5 | ## Trade-offs for PRISM DID method 6 | 7 | The PRISM DID method follows specific principles and trade-offs, which are important to consider. These principles include: 8 | 1. Strong Availability: All update and deactivation events in the PRISM DID method are posted on-chain, ensuring public accessibility. Once published, these events cannot be deleted from the blockchain, enabling historical auditability. 9 | 1. Decentralization: PRISM allows anyone to create, resolve, and manage their DIDs without requiring authorization from external parties. This ensures censorship-resistant actions and promotes decentralization. 10 | 1. Instant Creation: PRISM enables instant DID creation without requiring interaction with the blockchain, ensuring a seamless user experience. 11 | 1. DID Transferability: Due to the visibility and immutability of updates, PRISM DIDs support the exchange of ownership. 12 | 13 | ### Current Limitations 14 | 15 | The PRISM DID method has certain limitations that should be considered, including: 16 | - Latency: Performing DID update or deactivation events requires submitting a blockchain transaction, which introduces a delay between submission and resolution. 17 | - Throughput Limit: Posting events on-chain is subject to the space limits imposed by transaction and block sizes, potentially affecting the method's throughput. 18 | 19 | ## Steps to request a new feature 20 | 21 | When requesting new features for the PRISM DID method, it is important to ensure alignment with the method's design principles. Consider the following steps: 22 | 1. Describe the problem you aim to solve, focusing on the underlying use case rather than proposing the feature itself. 23 | 1. Clearly outline the intended new feature and its functionality. 24 | 1. Explain why your use cases cannot be accomplished without the proposed feature or, if possible, describe the trade-offs between the new feature and the current approach. 25 | 1. Suggest potential implementation paths for the new feature. 26 | 1. Justify how the new feature aligns with the principles of the PRISM DID method and demonstrate that it maintains the method's key properties without breaking them. 27 | 28 | By following these steps, you can effectively initiate a conversation around requesting new features for the PRISM DID method, ensuring that they enhance functionality and mitigate existing trade-offs while adhering to the method's core principles. 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2023 Input Output Global Inc (IOG) 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## DID:PRISM Specification 2 | 3 | This repository holds the [specification of the `prism` DID method](https://github.com/input-output-hk/prism-did-method-spec/blob/main/w3c-spec/PRISM-method.md), and defines data models and protocol rules to create, manage, and resolve Decentralised Identifiers (DIDs) on top of the Cardano blockchain as its verifiable data registry, where DID's information is stored. 4 | 5 | [Decentralized identifiers](https://www.w3.org/TR/did-core) (DIDs) are a type of identifier that enables verifiable, decentralized digital identity. A DID refers to any subject (for example, a person, organization, thing, data model, abstract entity, and so on) as determined by the controller of the DID. 6 | 7 | The `prism` DID method is in constant evolution, and future versions are under development. It is recommended to any implementer to follow the evolution of the new versions closely to understand, comment on, and/or suggest future changes via issues and PRs. 8 | 9 | ### Extensions 10 | 11 | The `prism` DID method serves as a building block for other protocols and applications. Refer to the [extensions folder](https://github.com/input-output-hk/prism-did-method-spec/tree/main/extensions) to find related specifications that build upon this DID method. 12 | -------------------------------------------------------------------------------- /extensions/did-prism-anoncreds-object-method-specification.md: -------------------------------------------------------------------------------- 1 | # did:prism AnonCreds Method v1.0 2 | 3 | ## Objective of this specification 4 | 5 | The primary goal of this document is to describe a way to identify and retrieve resources using the [PRISM DID method](https://github.com/input-output-hk/prism-did-method-spec/blob/main/w3c-spec/PRISM-method.md) as a building block. 6 | As a secondary set of goals we want our approach to be re-usable by other DID methods that share some basic functionalities. Finally, we will present a component we identify as useful in the interoperability of [AnonCred Methods](https://hyperledger.github.io/anoncreds-methods-registry/). 7 | 8 | ## Definitions 9 | 10 | In this section we will present a way to classify the resources we want to treat, and then define the properties we want to satisfy with our AnonCred method. 11 | 12 | Within the context of SSI, different resources are involved in the creation, sharing and verification of [Verifiable Credentials (VCs)](https://www.w3.org/TR/vc-data-model-2.0/) (including [AnonCreds](https://hyperledger.github.io/anoncreds-spec/)). Some of these resources can be described as _static_ due to the fact that they do not change once associated to a VC. Examples of these resources are schema objects and credential definitions. Conversely, other resources could be described as _dynamic_ in nature, as VCs refer to them, but the resources per se are updated over time. Examples of these resources are status lists or revocation entries. This classification will help us later in this document to describe how to identify, retrieve and validate resources in accordance to this specification. 13 | 14 | As a whole, this AnonCred method aims to provide: 15 | - Integrity assurance for static resources: When a user retrieves a resource based on its identifier, he can validate on his own that the resource hasn't been tampered with. 16 | - Storage layer extensibility: after resource creation, the user controlling the resource identifier can update the storage location and/or retrieval protocols without affecting the original resource identifier. 17 | 18 | ### Underlying DID method properties 19 | 20 | As we mentioned in the objectives section, we will define the AnonCred method in terms of the PRISM DID method. However, the AnonCred method could be constructed on top of any [DID method](https://www.w3.org/TR/did-core/) that simply supports for [services](https://www.w3.org/TR/did-core/#services). 21 | 22 | In the remaining of this document we will take the following conventions: 23 | 24 | - For each resource, the user has an intended URL where the resource should be retrieved. We will call this URL `baseURL`. 25 | - Each resource has an associated DID, `userDID`, that is used to create the resource identifier. 26 | - The `userDID` has a service we will call `baseService`. 27 | + The `baseService` has `baseURL` as its only service endpoint. 28 | + The `baseService` has type `LinkedResourceV1`. 29 | - The resource has an optional associated path `resourcePath` relative to its `baseURL`. 30 | 31 | As an illustrative example we can show the following DID document where: 32 | - `userDID` is `did:prism:123...abc` 33 | - `baseService` is `did:prism:123...abc#service1` 34 | - `baseURL` is `https://example.com` 35 | 36 | Based on those values, we can present the following DID document: 37 | 38 | ```json 39 | { 40 | "id" : "did:prism:123...abc", 41 | ... 42 | "service" : [ 43 | { 44 | "id" : "did:prism:123...abc#service1", 45 | "type : "LinkedResourceV1", 46 | "serviceEndpoint" : "https://example.com" 47 | } 48 | ] 49 | } 50 | ``` 51 | 52 | ## Identifiers 53 | 54 | In this section we will define the identifiers structure for any given resource. 55 | We will start describing a basic identifier where each resource uses its own service, and later explain how to optionally share the same service between multiple resources. 56 | We will also distinguish the identifier construction for static and dynamic resources. 57 | 58 | ### Dynamic resources 59 | 60 | For dynamic resources such as revocation lists, we define the identifiers in this AnonCred method as follows. 61 | Given a resource `r` with associated `userDID` and base service `baseService`, we can define `r`'s identifier as follows: 62 | 63 | ``` 64 | userDID ? resourceService= baseService 65 | ``` 66 | 67 | 68 | ### Static resources 69 | 70 | For static resources, like a schema or a credential definition, we don't usually have an integrity check at application layer. For this reason, we add to identifiers a `resourceHash` section that will serve for this purpose. 71 | Hence, given a resource `r` with associated `userDID` and base service `baseService`, we can define `r`'s identifier as follows: 72 | 73 | ``` 74 | userDID ? resourceService= baseService & resourceHash = encoded_hash(r) 75 | ``` 76 | 77 | where `encoded_hash` is the hex encoded SHA256 hash of the base64URL encoded resource `r`. 78 | 79 | #### A comment about resource authenticity 80 | 81 | We want to remark that, in practice, dynamic resources in SSI are signed by a key associated to the issuer of an associated VC. For this reason, this AnonCred method does not add any integrity nor authenticity check for dynamic resources, and leaves it to the application layer. In this way, we avoid an overhead for a second authenticity/integrity check at this stage. 82 | 83 | On a related note, we want to point out that we are also not performing an authenticity checks for static resources inside the AnonCred method. The reason is that in the context of VCs, the identifiers we are generating are attached in signed objects (the VCs). By attaching an integrity check to the identifier, we are inheriting the authenticity of the expected resource from the signature on the VC that contained the identifier in the first place. Future versions of this AnonCred method could add more flexibility by embedding a signature in the resource envelope (see below). However, we argue that the relevant authenticity property is attached to the authenticity of the identifier, and not the authenticity of the indirect resource it represents (see Future Work section). 84 | 85 | ### Sharing a common `baseURL` 86 | 87 | There are situations where a user controlling multiple resources would prefer to share the same service to support many of those resources. The advantage is to require less services in the `userDID` document, where the trade off is that updating the service for one resource will imply updating the location of all the resources. We consider that this trade off is use case dependent, and we added the following structure to support both scenarios. 88 | 89 | If a user wants to share the same `resourceService` and consequently `baseURL` for multiple resources, they have the option to distinguish specific resources by relative path from the `baseURL`. The relative path is specified with the `resourcePath` query parameter. As an example, if we imagine resources `r1` and `r2` located at `https://example.com/resources/r1.txt` and `https://example.com/resources/r2.txt` respectively. We can use the `baseURL` `https://example.com` and relative paths `/resource/r1.txt` and `/resource/r2.txt` respectively to obtain the following identifiers. 90 | 91 | ``` 92 | userDID ?resourceService= resourceService & resourcePath= /resource/r1.txt 93 | ``` 94 | and 95 | 96 | ``` 97 | userDID ?resourceService= resourceService & resourcePath= /resource/r2.txt 98 | ``` 99 | 100 | If the resources require a hash, the corresponding query parameter would also be included. Namely: 101 | 102 | ``` 103 | userDID ?resourceService= resourceService & resourcePath= /resource/r2.txt & resourceHash= encoded_hash(r2.txt) 104 | ``` 105 | 106 | where `encoded_hash`, once again, represents the hex encoded sha256 hash of the base64URL encodedresource. 107 | 108 | ## Resources retrieval 109 | 110 | In this section, we will describe how to obtain a resource from a given identifier in this AnonCred method. 111 | We start by briefly displaying some example identifiers using the DIDs from the PRISM DID method. 112 | 113 | ``` 114 | // Example 1 115 | did:prism:abc...123?resourceService="service1"&resourcePath="/schemas/123.json"&resourceHash="hffa...31aef3" 116 | 117 | // Example 2 118 | did:prism:abc...123?resourceService="service1"&resourcePath="/schemas/123.json" 119 | 120 | //Example 3: long form DID with all parameters 121 | did:prism:abc...123:abfha...1d4s8fr?resourceService="service1"&resourcePath="/schemas/123.json"&resourceHash="hffa...31aef3" 122 | ``` 123 | 124 | An implementation of this specification will behave as follows: 125 | - First, extract the PRISM DID from the URI. In examples 1 and 2, this is `did:prism:abc...123`, while in example 4, this is `did:prism:abc...123:abfha...1d4s8fr` 126 | - Then, resolve the extracted DID from the previous step. 127 | - The resolution will return a DID document. The implementation should validate that: 128 | - Given the value of `resourceService` parameter, lets call it `serviceId`. There must be a service with name `#serviceId` in the resolved DID document. In examples 1 and 2, this is a service with id equal to `did:prism:abc...123#service1`. See example DID document at the end of this section. 129 | - the identified service type must be `LinkedResourceV1`. 130 | - The `serviceEndpoint` of the service previously identified will contain a URL, lets call it `baseURL` in the next steps. 131 | - If present, attach to `baseURL` the value in `resourcePath`. For the examples shown above, this would lead to a URL `baseURL/schemas/123.json` 132 | - Perform a GET call to that URL to obtain the resource, expecting a JSON response. 133 | The resource will be encoded as a base64url string inside a JSON object of the form: 134 | 135 | ``` 136 | { 137 | “resource“ : 138 | } 139 | ``` 140 | - if the initial identifier contains a `resourceHash` query parameter, validate that the hex encoded sha256 hash of the string located in the `“response“` field of the JSON, matches the string in the `resourceHash` value. 141 | - If all the validations in the previous steps were correct, then return the JSON that it received from the server that generated the JSON response. 142 | 143 | Example DID document to illustrate an expected service: 144 | 145 | ``` 146 | { 147 | "id": "did:prism:abc...123", 148 | ... 149 | "service" : [ 150 | { 151 | "id" : "did:prism:abc...123#service1", 152 | "type": "LinkedResourceV1", 153 | "serviceEndpoint" : baseUrl 154 | } 155 | ] 156 | } 157 | ``` 158 | 159 | ## Universal AnonCred method resolver 160 | 161 | We want to dedicate a section to discuss an additional component, which is not exactly part of this AnonCred Method, but sits on top of it. 162 | 163 | Different methods in the AnonCreds method registry have described custom formats to represent resources when they are queried. Some methods return the resource as a string in a verbatim form. Other methods, including this one, create an envelope JSON object that contains the resources under some encoding (Base58, Base64URL, etc). From an application point of view, this format discrepancies hinders interoperability. We would like to describe here a simple layer that most application will have to implement in one form or another. We can call this component AnonCred method universal resolver. The sole purpose of this layer is to receive a URI that identifies a resources, and return the string resource without any envelope. The input URI can represent an identifier from any AnonCred method. The universal resolver would determine from the URI which specific AnonCred method it should call, and after receiving the response, it would do any needed validation (e.g. integrity checks), and remove the potential specific envelope that any method may be adding. 164 | 165 | We want to remark that URIs would need to contain enough information to distinguish which AnonCred method needs to be called. We could suggest to have in the URIs a reserved query parameter to identify the method (see Future Work section). 166 | 167 | ## Future Work 168 | 169 | This section is kept to list possible extensions for this AnonCred method 170 | 171 | ### Add a `resourceMethod` query parameter 172 | 173 | As mentioned in the previous section, we have observed the lack of clear ways to identify how to resolve a resource based solely on an identifier. Several AnonCred methods use a DID URL as resource identifier. However, they all follow slightly different resource resolution steps. We consider useful, if not actually needed, to add a `resourceMethod` query parameter to identify the AnonCred method that the identifier is representing. This allows a clear mechanism to take an identifier and determine how to treat it. It is also a good tool to identify a version of the AnonCred method the identifier is associated with. In the future of this specification, we may add new features where version names like `DIDURLsV1`, `DIDURLsV2`, etc. may become handy. 174 | 175 | ### More flexible `serviceEndpoint` values 176 | 177 | In this first conservative version, our method allows for a single URL for `serviceEndpoint` in the associated `resourceService`. However, one could see many alternatives to this: 178 | - Allow a list to represent a fallback semantics. 179 | - Allow other URIs for resources. For example, IPFS identifiers for static resources. 180 | - Allow a map to represent richer data. For example, a way to specify holder to verifier direct transmission of a resource. 181 | 182 | ### More flexible authenticity checks 183 | 184 | In order to add another layer for authenticity checks, the resource identifier would need to add a reference to a key, while the resource envelope would need a signature field. 185 | For example, the resource identifier could have query parameters `signingKey` and `signingAlgorithm`. Where `signingKey` could be either an encoded key or a DID URL that refers to a verification method. Key encodings and signing algorithms should be standardize. The JSON response that serves as resource envelope would add an adequate `proof` field containing necessary signature information. 186 | 187 | In that way, the user would be able to validate expected source for the resource. We want to note that the information related to the signing key is added in the identifier, in order to avoid the server side of the resolution process to tamper the key. However, we also need to remark that the resource identifier itself is susceptible to be modified, meaning that a user trying to dereference an identifier needs to trust the identifier's source in the first place. This implies that the authenticity of the resource is strongly tight to the authenticity of the identifier, and this is why we consider that supporting an integrity check between an static resource and its identifier is enough for this AnonCred method. Adding the authenticity at resource level does not solve the more relevant identifier authenticity, which is the likely intended property to preserve. 188 | 189 | For the case of dynamic resources, we remind the reader that existing SSI protocols already enforce a signature checks at application layer, but we would find it adequate to have an optional authenticity check for the case of dynamic resources in the future version of this AnonCred method. 190 | 191 | ### Passing query parameters to the underlying server 192 | 193 | For the case of dynamic resources, some use cases could benefit from the addition of historical versions of a resource. If the underlying server hosting the resources keeps their historical changes and exposes them through query parameters in an API, we find it relevant to support in future versions of this method a mechanism to forward that query to the server. We point out, that this additional query string is part of the identifier per se, as each user may decide to specify different parameters. What would be needed are two main parts: 194 | - An standard query string to send to an underlying server. For instance, `resourceVersion`. 195 | - A way to indicate this AnonCred method that the query parameter should be attached to the final URL before making the resource request. 196 | 197 | -------------------------------------------------------------------------------- /w3c-spec/PRISM-method.md: -------------------------------------------------------------------------------- 1 | # DID Method Specification - did:prism 2 | 3 | ## Status of this document 4 | 5 | This document describes the first version of the `prism` DID method. 6 | The method is in constant evolution, and future versions are under development. 7 | It is recommended to any implementer to follow the evolution of the new versions closely 8 | to understand, comment on, and/or suggest future changes. 9 | 10 | ## Abstract 11 | 12 | The `prism` DID method defines data models and protocol rules to create, manage, and resolve Decentralized Identifiers (DIDs). The protocol is defined on top of the Cardano blockchain as its verifiable data registry, where DID's information is stored. 13 | 14 | The method is defined as a protocol, that describes operations, serialization formats, and rules. The protocol describes how to manage the lifecycle of DIDs and their associated DID documents. We will use the term `PRISM node` to refer to software that implements the protocol defined in this document. 15 | 16 | We would like to remark that, any reference to the blockchain such as, "Cardano network", "underlying chain", "on-chain", "ledger", "the blockchain" and similar ones throughout this document refer to **Cardano mainnet** unless explicitly said otherwise. The `prism` DID method, in its current form, solely depends on and uses Cardano mainnet. 17 | 18 | ## Versioning and protocol parameters 19 | 20 | This document describes the first version of the `prism` DID method. Each version of the protocol defines certain parameters such as hashing algorithms, encoding functions, size limits for data, and others. Each version MAY make changes to these set of parameters. For this version, considered the first one, the protocol parameters are the following: 21 | 22 | 23 | | Parameter | Description | Value | 24 | | -------- | -------- | -------- | 25 | | `SYSTEM_UPDATE_DID` | The DID that has the power to trigger a protocol update. | TBD`*` | 26 | | `GENESIS_PRISM_BLOCK` | The block number from which the `prism` DID method begins reading the blockchain. | TBD`*` | 27 | | `SECURE_DEPTH` | Number of confirmations a block needs to wait to not be rollbacked with high probability. | 112 blocks | 28 | | `PROTOBUF_VERSION` | The version of Protocol Buffers used. | 3 | 29 | | `CRYPTOGRAPHIC_CURVE` | Cryptographic curve used with a key. | secp256k1 | 30 | | `SIGNATURE_ALGORITHM` | Asymmetric public key signature algorithm. | SHA256 with ECDSA | 31 | | `HASH_ALGORITHM` | Algorithm for generating hashes. | SHA-256 | 32 | | `SHORT_ENCODING_ALGORITHM` | Encoding selected for various data (signatures, hashes, etc.). | HEX | 33 | | `LONG_ENCODING_ALGORITHM` | Encoding selected for Protobuf binary. | base64URL | 34 | | `MAX_ID_SIZE` | Maximum number of characters for an `id` field value. | 50 | 35 | | `MAX_TYPE_SIZE` | Maximum number of characters for a `type` field value. | 100 | 36 | | `MAX_SERVICE_ENDPOINT_SIZE` | Maximum number of characters for a `serviceEndpoint` field value. | 300 | 37 | | `MAX_SERVICE_NUMBER` | Maximum number of active services a DID Document can have at the same time. | 50 | 38 | | `MAX_VERIFICATION_METHOD_NUMBER` | Maximum number of active keys a DID can have at the same time. Note that this includes keys with `usage` `MASTER_KEY` and `REVOCATION_KEY` | 50 | 39 | | `SECP256K1_CURVE_NAME` | String identifier for the SECP256K1 eliptic curve | "secp256k1" | 40 | | `ED25519_CURVE_NAME` | String identifier for the ED25519 eliptic curve | "Ed25519" | 41 | | `X25519_CURVE_NAME` | String identifier for the Curve25519 eliptic curve | "X25519" | 42 | 43 | `*` These values will be filled in once the mainnet deployment is confirmed. 44 | 45 | ## DID Method Name 46 | 47 | The namestring that shall identify this DID `method-name` is: `prism`. 48 | 49 | A DID that uses this method MUST begin with the following prefix `did:prism`. The prefix MUST be in lowercase. 50 | 51 | The remainder of the DID is specified below. 52 | 53 | ### Method Specific Identifier 54 | 55 | ### Prism DID Method Syntax 56 | ```abnf 57 | prism-did = "did:prism:" initial-hash [encoded-state] 58 | initial-hash = 64HEXDIGIT 59 | encoded-state = ":" 1*id-char 60 | id-char = ALPHA / DIGIT / "-" / "_" 61 | ``` 62 | ### Examples of `did:prism` Identifiers 63 | 64 | The method defines two types of DIDs. The first one represents the DIDs that have been anchored on the blockchain following the protocol rules: 65 | ```abnf 66 | did:prism:9b5118411248d9663b6ab15128fba8106511230ff654e7514cdcc4ce919bde9b 67 | ``` 68 | The DID method specific identifier corresponds to the hex-encoded hash of the initial state of the DID document. 69 | 70 | The protocol also allows to create DIDs without interaction with the blockchain: 71 | ```abnf 72 | did:prism:9b5118411248d9663b6ab15128fba8106511230ff654e7514cdcc4ce919bde9b:Cj8KPRI7CgdtYXN0ZXIwEAFKLgoJc2VjcDI1NmsxEiEDHpf-yhIns-LP3tLvA8icC5FJ1ZlBwbllPtIdNZ3q0jU 73 | ``` 74 | For this case, the DID adds after the hash section, the actual encoded initial state. 75 | 76 | In the following sections we will describe more details about the state mentioned. 77 | 78 | ## DID Documents 79 | 80 | The `prism` DID method allows to create fairly expressive DID documents. In this first version, the method supports the creation of DID documents that contain arbitrary services. With respect to verification methods, all W3C verification relationships are supported (namely, authentication, key agreement, assertion, capability invocation and capability delegation). The supported verification method type is `JsonWebKey2020` where keys are expressed as JWK. The supported keys however are restricted to `ED25519_CURVE_NAME`, `SECP256K1_CURVE_NAME` and `X25519_CURVE_NAME`. In future versions, more verification method types and keys will be supported. 81 | 82 | 83 | ### EXAMPLE: DID document (JSON-LD) 84 | 85 | ```json 86 | { 87 | "@context": [ 88 | "https://www.w3.org/ns/did/v1", 89 | "https://w3id.org/security/suites/jws-2020/v1", 90 | "https://didcomm.org/messaging/contexts/v2", 91 | "https://identity.foundation/.well-known/did-configuration/v1" 92 | ], 93 | "id": "did:prism:db47e78dd57d2043a7a704fbd9d186a586682110a2097ac06dbc83b35602f290", 94 | "verificationMethod": [ 95 | { 96 | "id": "did:prism:db47e78dd57d2043a7a704fbd9d186a586682110a2097ac06dbc83b35602f290#authentication0", 97 | "type": "JsonWebKey2020", 98 | "controller": "did:prism:db47e78dd57d2043a7a704fbd9d186a586682110a2097ac06dbc83b35602f290", 99 | "publicKeyJwk": { 100 | "crv": "secp256k1", 101 | "kty": "EC", 102 | "x": "KDiRms8trMvdT4aaJbRtNxtDCYTvFJbstRrC3TgQNnc", 103 | "y": "AUwPe-RV4D9c9WbVKHeg7-imv2ZVDX8hDAEkMDphpqU" 104 | } 105 | }, 106 | { 107 | "id": "did:prism:db47e78dd57d2043a7a704fbd9d186a586682110a2097ac06dbc83b35602f290#issuing0", 108 | "type": "JsonWebKey2020", 109 | "controller": "did:prism:db47e78dd57d2043a7a704fbd9d186a586682110a2097ac06dbc83b35602f290", 110 | "publicKeyJwk" : { 111 | "kty" : "OKP", 112 | "crv" : "Ed25519", 113 | "x" : "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" 114 | } 115 | }, 116 | { 117 | "id": "did:prism:db47e78dd57d2043a7a704fbd9d186a586682110a2097ac06dbc83b35602f290#key-agreement0", 118 | "type": "JsonWebKey2020", 119 | "controller": "did:prism:db47e78dd57d2043a7a704fbd9d186a586682110a2097ac06dbc83b35602f290", 120 | "publicKeyJwk": { 121 | "kty": "OKP", 122 | "crv": "X25519", 123 | "x": "pE_mG098rdQjY3MKK2D5SUQ6ZOEW3a6Z6T7Z4SgnzCE" 124 | } 125 | } 126 | ], 127 | "authentication": [ 128 | "did:prism:db47e78dd57d2043a7a704fbd9d186a586682110a2097ac06dbc83b35602f290#authentication0" 129 | ], 130 | "assertionMethod": [ 131 | "did:prism:db47e78dd57d2043a7a704fbd9d186a586682110a2097ac06dbc83b35602f290#issuing0" 132 | ], 133 | "keyAgreement": [ 134 | "did:prism:db47e78dd57d2043a7a704fbd9d186a586682110a2097ac06dbc83b35602f290#key-agreement0" 135 | ], 136 | "service": [ 137 | { 138 | "id": "did:prism:db47e78dd57d2043a7a704fbd9d186a586682110a2097ac06dbc83b35602f290#DIDCommMessaging", 139 | "type": "DIDCommMessaging", 140 | "serviceEndpoint": [ 141 | { 142 | "uri": "https://example.com/path", 143 | "accept": [ "didcomm/v2", "didcomm/aip2;env=rfc587" ], 144 | "routingKeys": ["did:example:somemediator#somekey"] 145 | } 146 | ] 147 | }, 148 | { 149 | "id" : "did:prism:db47e78dd57d2043a7a704fbd9d186a586682110a2097ac06dbc83b35602f290#linked-domain-1", 150 | "type": "LinkedDomains", 151 | "serviceEndpoint": { 152 | "origins": ["https://foo.example.com", "https://identity.foundation"] 153 | } 154 | }, 155 | { 156 | "id": "did:prism:db47e78dd57d2043a7a704fbd9d186a586682110a2097ac06dbc83b35602f290#linked-domain-2", 157 | "type": "LinkedDomains", 158 | "serviceEndpoint": "https://bar.example.com" 159 | } 160 | ] 161 | } 162 | ``` 163 | 164 | ## High level protocol description 165 | 166 | At a high level, the protocol that defines the `prism` DID method works as follows: 167 | - Any user can run a `PRISM node`, to self validate information; or can rely on a set of actors that run nodes on his behalf. The level of delegation of trust is a decision made by each user. 168 | - Any user willing to create a DID can do so without any need to interact with any `PRISM node`. The creation of a DID can be optionally announced publicly by publishing a creation operation on-chain. The action of posting an operation on-chain does require the interaction with a `PRISM node` 169 | - Users can update the DID documents associated to their DIDs. To do this, they need to publish respective update operations on-chain. This again requires interaction with a `PRISM node` 170 | - Deactivation of a DID can be performed in the same lines of updates, but publishing a deactivation operation 171 | - `PRISM nodes` read the operations published on-chain, and maintain internally the map of DIDs to the history of changes of their associated DID documents. 172 | - Any client can query any `PRISM node` and obtain information (state) associated to a DID 173 | - DID resolvers, can take the output of `PRISM nodes` and construct the current DID document associated to a DID 174 | 175 | An additional consideration is that operations can be posted on-chain in blocks, helping on the scalability side and general reduction of fees. 176 | 177 | In the following sections we will describe in detail the format of operations and how to validate them. 178 | 179 | ## DID operations construction 180 | 181 | The `prism` method represents DID operations such as creation, updates and deactivations using [Protocol Buffers](https://developers.google.com/protocol-buffers). 182 | Each operation is defined as a message, which we will describe in the next sub-sections. 183 | 184 | At a high level, operations represent actions upon DIDs (i.e. Create, Update, Deactivate). Operations are batched in `blocks`, and blocks are wrapped in `objects`. Each object is attached to a transaction's metadata in the Cardano network. 185 | Users construct these operations and submit them to the blockchain. Conversely `PRISM nodes` read Cardano transactions, examine their metadata, they extract (when present) encoded objects. The objects contains blocks, and the block contains a sequence of DID operations. `PRISM nodes` validate and interpret these operations, which allow them to build their shared global view of the map of DIDs to DID documents. 186 | 187 | Below, we present the main protobuf messages mentioned before. For purpose of making the reading simple, we moved some protobuf definitions to an Appendix at the end of this specification. We will describe for each operation how users must construct them to be considered valid. 188 | 189 | 190 | ### Block and object definitions 191 | 192 | We first show the way to represent `blocks` of operations. 193 | 194 | ```protobuf 195 | // Represent a block that holds operations. 196 | message AtalaBlock { 197 | reserved 1; // Represents the version of the block. Deprecated 198 | repeated SignedAtalaOperation operations = 2; // A signed operation, necessary to post anything on the blockchain. 199 | } 200 | ``` 201 | 202 | As we mentioned before, `blocks` are wrapped inside `objects`, which will add some information about them in the planned future: 203 | 204 | ```protobuf 205 | // Wraps an AtalaBlock and its metadata. 206 | message AtalaObject { 207 | reserved 1, 2, 3; // Removed block_hash field. 208 | reserved "block_hash"; 209 | reserved "block_operation_count"; 210 | reserved "block_byte_length"; 211 | 212 | AtalaBlock block_content = 4; // The block content. 213 | } 214 | ``` 215 | 216 | When a user wants to perform DID operations, he: 217 | - Puts them in the desired order inside an `AtalaBlock`, 218 | - wraps the block in an `AtalaObject`. 219 | - Encodes the `AtalaObject` inside the metadata of a Cardano transaction (see Appendix A for encoding details) 220 | - Sends the transaction with metadata to the Cardano blockchain 221 | 222 | ### Signed operation definition 223 | 224 | The different DID operations are signed (as we will describe in next sections), the message that represents signed operations is: 225 | 226 | ```protobuf 227 | // A signed operation, necessary to post anything on the blockchain. 228 | message SignedAtalaOperation { 229 | string signed_with = 1; // The key ID used to sign the operation, it must belong to the DID that signs the operation. 230 | bytes signature = 2; // The actual signature. 231 | AtalaOperation operation = 3; // The operation that was signed. 232 | } 233 | 234 | // The possible operations affecting the blockchain. 235 | message AtalaOperation 236 | reserved 3, 4; // fields used by an extension of the protocol. Not relevant for the DID method 237 | // The actual operation. 238 | oneof operation { 239 | // Used to create DIDs. 240 | CreateDIDOperation create_did = 1; 241 | // Used to update an existing public DID. 242 | UpdateDIDOperation update_did = 2; 243 | // Used to announce new protocol update 244 | ProtocolVersionUpdateOperation protocol_version_update = 5; 245 | // Used to deactivate DID 246 | DeactivateDIDOperation deactivate_did = 6; 247 | }; 248 | } 249 | ``` 250 | **Construction rules** 251 | - all fields MUST be present. 252 | - The `operation` field MUST contain one of `CreateDIDOperation`, `UpdateDIDOperation` ,`DeactivateDIDOperation`, `ProtocolVersionUpdateOperation` which MUST be constructed correctly according to rules defined in the next sections of this document. 253 | 254 | Lets move to the specific description of each operation 255 | 256 | ### Create DID 257 | 258 | In order to create a DID, the controller will construct a `CreateDIDOperation` message using the following definitions. 259 | 260 | ```protobuf 261 | // The operation to create a public DID. 262 | message CreateDIDOperation { 263 | DIDCreationData did_data = 1; // DIDCreationData with public keys and services 264 | 265 | // The data necessary to create a DID. 266 | message DIDCreationData { 267 | reserved 1; // Removed DID id field which is empty on creation 268 | repeated PublicKey public_keys = 2; // The keys that belong to this DID Document. 269 | repeated Service services = 3; // The list of services that belong to this DID Document. 270 | repeated string context = 4; // The list of @context values to consider on JSON-LD representations 271 | } 272 | } 273 | 274 | // Represents a public key with metadata, necessary for a DID document. 275 | message PublicKey { 276 | reserved 3, 4, 5, 6; 277 | string id = 1; // The key identifier within the DID Document. 278 | KeyUsage usage = 2; // The key's purpose. 279 | 280 | // The key's representation. 281 | oneof key_data { 282 | ECKeyData ec_key_data = 8; // The Elliptic Curve (EC) key. 283 | CompressedECKeyData compressed_ec_key_data = 9; // Compressed Elliptic Curve (EC) key. 284 | }; 285 | } 286 | 287 | // Every key has a single purpose: 288 | enum KeyUsage { 289 | // UNKNOWN_KEY is an invalid value - Protobuf uses 0 if no value is provided and we want the user to explicitly choose the usage. 290 | UNKNOWN_KEY = 0; 291 | MASTER_KEY = 1; 292 | ISSUING_KEY = 2; 293 | KEY_AGREEMENT_KEY = 3; 294 | AUTHENTICATION_KEY = 4; 295 | REVOCATION_KEY = 5; 296 | CAPABILITY_INVOCATION_KEY = 6; 297 | CAPABILITY_DELEGATION_KEY = 7; 298 | } 299 | 300 | // Holds the necessary data to recover an Elliptic Curve (EC)'s public key. 301 | message ECKeyData { 302 | string curve = 1; // The curve name, e.g. secp256k1. 303 | bytes x = 2; // The x coordinate, represented as bytes. 304 | bytes y = 3; // The y coordinate, represented as bytes. 305 | } 306 | 307 | // Holds the compressed representation of data needed to recover Elliptic Curve (EC)'s public key. 308 | message CompressedECKeyData { 309 | string curve = 1; // The curve name, e.g. secp256k1. 310 | bytes data = 2; // compressed Elliptic Curve (EC) public key. 311 | } 312 | 313 | message Service { 314 | string id = 1; 315 | string type = 2; 316 | string service_endpoint = 3; 317 | } 318 | ``` 319 | 320 | **Construction rules** 321 | 322 | Below we see the rules to construct a well-formed `CreateDIDOperation`, these rules MUST be followed while creating the operation, and will be checked by `PRISM nodes`. Any construction error will make nodes ignore the operation. 323 | 324 | - The `did_data` field of the `CreateDIDOperation` MUST not be empty and MUST be properly constructed 325 | - Each element of `public_keys` and `services` fields MUST be constructed correctly 326 | - `services` can be empty. The list MUST not exceed `MAX_SERVICE_NUMBER` elements. 327 | - For each `Service` message in the list: 328 | - The `type` field MUST be a string or a non empty JSON array of strings. 329 | - The `type` value MUST not start nor end with whitespaces, and MUST have at least a non whitespace character. 330 | - The `type` value MUST not exceed `MAX_TYPE_SIZE` characters in length. 331 | - The strings constructing the value in `type` SHOULD be registered in the DID Specification Registries [DID-SPEC-REGISTRIES](https://www.w3.org/TR/did-spec-registries/) 332 | - To be more precise, the `type` value MUST conform the following ABNF rule 333 | 334 | type-value = type-string / "[" *(quoted-type-string ",") quoted-type-string "]" 335 | quoted-type-string = DQUOTE type-string DQUOTE 336 | type-string = type-char *(*SP type-char) 337 | type-char = ALPHA / DIGIT / "-" / "_" 338 | - The `id` field MUST be a valid [fragment](https://www.rfc-editor.org/rfc/rfc3986#section-3.5) string in accordance to [DID URL syntax](https://www.w3.org/TR/did-core/#did-url-syntax). The `id` MUST not exceed `MAX_ID_SIZE` characters in length. 339 | - The `service_endpoint` field MUST contain one of: 340 | - a URI 341 | - a JSON object 342 | - a non-empty JSON array of URIs and/or JSON objects 343 | - all mentioned URIs MUST conform to [RFC3986](https://www.rfc-editor.org/rfc/rfc3986) and normalized according to the [Normalization and Comparison rules in RFC3986 and to any normalization](https://www.rfc-editor.org/rfc/rfc3986#section-6) rules in its applicable URI scheme specification 344 | - The `service_endpoint` value MUST not exceed `MAX_SERVICE_ENDPOINT_SIZE` characters in length 345 | - The `public_keys` field MUST contain at least one `PublicKey` message for which its `usage` field MUST be `MASTER_KEY`. 346 | - `public_keys` MUST not exceed `MAX_VERIFICATION_METHOD_NUMBER` elements. 347 | - For each `PublicKey` message: 348 | - The `id` field MUST follow the same rules as `id`s for `Service`s 349 | - The `usage` field MUST not be `UNKNOWN_KEY` 350 | - The remaining key usages are: 351 | - `MASTER_KEY`: Used to sign `AtalaOperation`s. It won't appear in DID documents during resolution 352 | - `ISSUING_KEY`: equivalent to `assertion` verification relationship 353 | - `REVOCATION_KEY`: Used for related protocol outside of this spec. It will not appear in the DID documents. 354 | - `KEY_AGREEMENT_KEY`, `AUTHENTICATION_KEY`, `CAPABILITY_INVOCATION_KEY` and `CAPABILITY_DELEGATION_KEY`: represent their mirror verification relationship according to DID core specification. 355 | - `key_data` field MUST not be empty 356 | - If it contains an `ECKeyData` 357 | - The `curve` MUST be one of `SECP256K1_CURVE_NAME`, `ED25519_CURVE_NAME` or `X25519_CURVE_NAME`. When the `usage` is `MASTER_KEY` the `curve` MUST be `SECP256K1_CURVE_NAME`. 358 | - The `x` MUST contain a valid `x` coordinate of the public key 359 | - The `y` MUST contain a valid `y` coordinate of the public key 360 | - If it contains a `CompressedECKeyData` 361 | - The `curve` MUST be one of `SECP256K1_CURVE_NAME`, `ED25519_CURVE_NAME` or `X25519_CURVE_NAME`. When the `usage` is `MASTER_KEY` the `curve` MUST be `SECP256K1_CURVE_NAME`. 362 | - The `x` MUST contain a valid compressed representation of the public key in accordance to the `curve` value 363 | - `context` field MUST contain a list of strings. Each string represents additional `@context` values that the resolver will use to produce JSON-LD output during DID Document generation. The list MUST NOT contain repeated values. 364 | 365 | **Signing and submission** 366 | 1. Once the controller created the `CreateDIDOperation`, he can construct an `AtalaOperation` and sign it using the `SIGNATURE_ALGORITHM`. With that, construct a `SignedAtalaOperation` that contains the generated signature in the `signature` field; the `AtalaOperation` in the `operation` field, and the key identifier of the master key used to generate this signature in `signed_with`. Note that the `usage` of the key used to sign the operation MUST be `MASTER_KEY` 367 | 2. With the `SignedAtalaOperation` the user can decide to create an `AtalaBlock` and `AtalaObject` messages, and submit a transaction himself containing the operation. Alternatively, he can gather more operations before submitting a transaction in order to save fees. 368 | 3. Once the transaction containing the operation has a `SECURE_DEPTH` in the blockchain, then the operation will be processed by `PRISM nodes`. 369 | 4. The DID generated will have `did:prism:` as prefix, the method specific identifier is produced by applying the `SHORT_ENCODING_ALGORITHM` to the hash produced by the `HASHING_ALGORITHM` applied to the binary representation of the `AtalaOperation` that was signed. We call these DIDs, short form DIDs. 370 | 371 | #### Long form DIDs (unpublished DIDs) 372 | 373 | The `prism` DID method also supports the creation of DIDs without the need of publishing operations on-chain. 374 | To do so: 375 | - create the `CreateDIDOperation` as described in the previous section. 376 | - Then, compute the DID as described in step 4. 377 | - Finally, use the `LONG_ENCODING_ALGORITHM` on the binary representation of the `AtalaOperation` that contains the `CreateDIDOperation` built before, and append this string with an starting `":"` at the end of the original DID from step 4. 378 | 379 | This DID, called Long Form DID, can later be published (by posting the corresponding `CreateDIDOperation` as described in the previous section), and updated by the controller. In the meantime, the user will be able to use it without submitting anything to the blockchain. 380 | 381 | Note that each long form DID in the `prism` DID method has a unique short form associated DID. We can validate the correspondence by computing the hash of the encoded `AtalaOperation` in the long form DID, and checking its equality with the last section of the short form DID. 382 | Also note, that a short form DID is a prefix of its corresponding long form DID. 383 | 384 | 385 | ### Read DID 386 | 387 | In this section we will describe the read process at a high level. For more details, refer to the section that translate internal state to DID documents later in this document. 388 | 389 | `PRISM nodes` are constantly reading the underlying blockchain in search of transactions that contain `AtalaObjects` in their metadata. These nodes read the operations extracted from the objects, validate them and construct a map of DIDs to their corresponding DID documents. The rules by which `PRISM nodes` do this are described later in this text. 390 | 391 | When a `PRISM node` receives a DID for resolution, it first checks if it is a short or long form DID. 392 | 393 | If it is a short form: 394 | * It checks if there is information about its corresponding DID document on its database. 395 | * If there is, it returns the latest state known to it. 396 | * If there is no information, the resolution returns the `notFound` error 397 | 398 | If the DID to resolve is in long form: 399 | * It extracts the short form DID from the DID 400 | * It validates the short form DID correspondence (i.e. the encoded operation must match the expected hash), and validates that the decoded operation is a well constructed `AtalaOperation` that contains a `CreateDIDOperation` 401 | * If the validations fail, it returns the `invalidDid` error. 402 | * It then checks if there is information about the DID document corresponding to the short DID on its database. 403 | * If there is, it returns the data on its database. 404 | * If there is no information, it decodes the `AtalaOperation` from the long form DID, and returns the corresponding DID document. See next sections to find the translation between protobuf models and DID documents as well as DID document metadata. 405 | 406 | 407 | ### Update DID 408 | 409 | In order to update the DID document associated with a DID, the initial `CreateDIDOperation` must be already published. For the case of long form `prism` DIDs, the user can first create the corresponding `SignedAtalaOperation` for the DID creation; then, the intended update operation and finally submit both corresponding signed operations in a single transaction by gathering the two in the same `AtalaBlock`. 410 | 411 | The model for `UpdateDIDOperation`s looks as follows: 412 | 413 | ```protobuf 414 | // Specifies the necessary data to update a public DID. 415 | message UpdateDIDOperation { 416 | bytes previous_operation_hash = 1; // The hash of the most recent operation that was used to create or update the DID. 417 | string id = 2; 418 | repeated UpdateDIDAction actions = 3; // The actual updates to perform on the DID. 419 | } 420 | ``` 421 | 422 | where 423 | 424 | ```protobuf 425 | // The potential details that can be updated in a DID. 426 | message UpdateDIDAction { 427 | 428 | // The action to perform. 429 | oneof action { 430 | AddKeyAction add_key = 1; // Used to add a new key to the DID. 431 | RemoveKeyAction remove_key = 2; // Used to remove a key from the DID. 432 | AddServiceAction add_service = 3; // Used to add a new service to a DID, 433 | RemoveServiceAction remove_service = 4; // Used to remove an existing service from a DID, 434 | UpdateServiceAction update_service = 5; // Used to Update a list of service endpoints of a given service on a given DID. 435 | PatchContextAction patch_context = 6; // Used to Update a list of `@context` strings used during resolution for a given DID. 436 | } 437 | } 438 | 439 | // The necessary data to add a key to a DID. 440 | message AddKeyAction { 441 | PublicKey key = 1; // The key to include. 442 | } 443 | 444 | // The necessary data to remove a key from a DID. 445 | message RemoveKeyAction { 446 | string keyId = 1; // the key id to remove 447 | } 448 | 449 | message AddServiceAction { 450 | Service service = 1; 451 | } 452 | 453 | message RemoveServiceAction { 454 | string serviceId = 1; 455 | } 456 | 457 | message UpdateServiceAction { 458 | string serviceId = 1; // scoped to the did, unique per did 459 | string type = 2; // new type if provided 460 | // Will replace all existing service endpoints of the service with provided ones 461 | string service_endpoints = 3; 462 | } 463 | 464 | message PatchContextAction { 465 | repeated string context = 1; // The list of strings to use by resolvers during resolution when producing a JSON-LD output 466 | } 467 | ``` 468 | 469 | **Construction rules** 470 | * The `previous_operation_hash` field MUST contain the hash of the last `AtalaOperation` message that was used to successfully update the DID. This is, the hash of the `AtalaOperation` that contained the last `UpdateDIDOperation` applied or, if this is the first update, the hash of the `AtalaOperation` that contained the `CreateDIDOperation` that created the DID. The hash is obtained through the `HASHING_ALGORITHM` of the said message. 471 | * The `id` field MUST contain the DID method-specific identifier of the short form DID that the update operation intends to update. 472 | - The `actions` field MUST contain a non empty list of well constructed `UpdateDIDAction`s 473 | - If the action is: 474 | - `AddKeyAction`, the `key` field MUST not be empty, and it MUST contain a well formed `PublicKey` as described in previous sections 475 | - `RemoveKeyAction`, the `keyId` field MUST contain the `id` of the `PublicKey` to delete from the DID document 476 | - `AddServiceAction`, the `service` field MUST not be empty, and it MUST contain a well constructed `Service` message as described in previous sections 477 | - `RemoveServiceAction`, the `serviceId` field MUST contain the `id` of the service to remove from the DID document 478 | - `UpdateServiceAction`: 479 | - the `serviceId` field MUST contain the `id` of the service to update 480 | - it MUST NOT be the case that both, the `type` field and `service_endpoint` field are empty. This is, at least one of this field MUST be correctly populated 481 | - the `type` field, if present, MUST follow the same rules of the `type` field of the `Service` message. The `type` represents the new type (if present) that will replace the existing one. 482 | - the `service_endpoints` field, if non empty, MUST conform the same rules as the `service_endpoint` field of the `Service` message 483 | - `PatchContextAction`, the `context` field MUST contain a list of strings. The list MUST not contain repeated elements 484 | 485 | **Signing and submission** 486 | 1. Once the controller created the `UpdateDIDOperation`, he can construct an `AtalaOperation` and sign it using the `SIGNATURE_ALGORITHM`. With that, construct a `SignedAtalaOperation` that contains the generated signature in the `signature` field; the `AtalaOperation` in the `operation` field, and the key identifier of the master key used to generate this signature in `signed_with` 487 | 2. With the `SignedAtalaOperation` the user can decide to create an `AtalaBlock` and `AtalaObject` messages, and submit a transaction himself containing the operation. Alternatively, he can gather more operations before submitting a transaction in order to save fees. 488 | 3. Once the transaction containing the operation has a `SECURE_DEPTH` in the blockchain, then the operation will be processed by `PRISM nodes`. 489 | 490 | 491 | ### Deactivate DID 492 | 493 | In order to deactivate a DID, the controller can build an instance of `DeactivateDIDOperation` 494 | 495 | ```protobuf 496 | message DeactivateDIDOperation { 497 | bytes previous_operation_hash = 1; // The hash of the most recent operation that was used to create or update the DID. 498 | string id = 2; // DID method-specific identifier of the DID to be deactivated 499 | } 500 | ``` 501 | 502 | **Construction rules** 503 | * The `previous_operation_hash` field MUST conform to the same rules as the `previous_operation_hash` field in `UpdateDIDOperation` 504 | * The `id` field MUST conform the same rules as the `id` field in `UpdateDIDOperation` 505 | 506 | **Signing and submission** 507 | 1. Once the controller created the `DeactivateDIDOperation`, he can construct an `AtalaOperation` and sign it using the `SIGNATURE_ALGORITHM`. With that, construct a `SignedAtalaOperation` that contains the generated signature in the `signature` field; the `AtalaOperation` in the `operation` field, and the key identifier of the master key used to generate this signature in `signed_with` 508 | 2. With the `SignedAtalaOperation` the user can decide to create an `AtalaBlock` and `AtalaObject` messages, and submit a transaction himself containing the operation. Alternatively, he can gather more operations before submitting a transaction in order to save fees. 509 | 3. Once the transaction containing the operation has a `SECURE_DEPTH` in the blockchain, then the operation will be processed by `PRISM nodes`. 510 | 511 | ### Protocol Version Update 512 | 513 | As a coordination mechanism, the protocol defines a message to trigger protocol updates. A protocol update triggers a version change of the protocol. Such change can lead to different protocol parameters, formats, or even modify operations. 514 | The messages associated to a protocol update are: 515 | 516 | ```protobuf 517 | // Specifies the protocol version update 518 | message ProtocolVersionUpdateOperation { 519 | string proposer_did = 1; // The DID method-specific identifier that proposes the protocol update. 520 | ProtocolVersionInfo version = 2; // Information of the new version 521 | } 522 | 523 | message ProtocolVersionInfo { 524 | reserved 2, 3; 525 | string version_name = 1; // (optional) name of the version 526 | int32 effective_since = 4; // Cardano block number that tells since which block the update is enforced 527 | 528 | // New major and minor version to be announced, 529 | // If major value changes, the node MUST stop issuing and reading operations, and upgrade before `effective_since` because the new protocol version. 530 | // If minor value changes, the node can opt to not update. All events _published_ by this node would be also 531 | // understood by other nodes with the same major version. However, there may be new events that this node won't _read_ 532 | ProtocolVersion protocol_version = 5; 533 | } 534 | 535 | message ProtocolVersion { 536 | // Represent the major version 537 | int32 major_version = 1; 538 | // Represent the minor version 539 | int32 minor_version = 2; 540 | } 541 | ``` 542 | 543 | **Construction rules** 544 | - The `proposer_did` MUST be the method specific identifier of the `SYSTEM_UPDATE_DID` 545 | - The `version` MUST not be empty, and MUST contain a well constructed `ProtocolVersionInfo` message 546 | - The `ProtocolVersionInfo` message: 547 | - `version_name` can be empty, and represents an optional name of the version 548 | - `effective_since` MUST be possitive, and describes which is the first Cardano block where the new version becomes effective, all operations found on-chain from that block (included) MUST follow the new protocol version. 549 | - The `ProtocolVersion` MUST contain positive integrs in `major_version` and `minor_version` fields 550 | - A change of the `major_version` implies that the protocol changes in a way in which current DID operations will become invalid. For example, the format of an operation changes 551 | - A change of the `minor_version` implies that the protocol changes in a way in which current DID operations will still be valid, but other new changes may not be properly processed by `PRISM nodes` running old versions of the protocol. For example, a new operation is added, or a new `KeyUsage` is supported. 552 | 553 | Note that `minor_version` changes inform node operators that they may be able to skip the update, as their nodes will still interpret what they need. 554 | 555 | **Signing and submission** 556 | 1. Once the administrator created the `ProtocolVersionUpdateOperation`, he can construct an `AtalaOperation` and sign it using the `SIGNATURE_ALGORITHM`. With that, construct a `SignedAtalaOperation` that contains the generated signature in the `signature` field; the `AtalaOperation` in the `operation` field, and the key identifier of the master key used to generate this signature in `signed_with` 557 | 2. With the `SignedAtalaOperation` the user can decide to create an `AtalaBlock` and `AtalaObject` messages, and submit a transaction himself containing the operation. Alternatively, he can gather more operations before submitting a transaction in order to save fees. 558 | 3. Once the transaction containing the operation has a `SECURE_DEPTH` in the blockchain, then the operation will be processed by `PRISM nodes`. 559 | 560 | 561 | ## Processing of operations 562 | 563 | Up until now, we have described the rules to construct operations. However, we haven't described how the operations are processed and interpreted by `PRISM nodes`. 564 | 565 | We described that operations are grouped in `blocks`, which are wrapped in `objects`, and they are submitted as part of transactions metadata. In the Cardano blockchain, transactions are added to the chain in blocks. Each Cardano block is added on top of the previous block. A transaction is added to the blockchain when a block that contains it is added to the blockchain. We say that a block gets a confirmation when a new block is added to the chain on top of it. In the same lines, we say that a transaction added to the blockchain gets a confirmation, when the block that contains the transaction receives a confirmation. 566 | 567 | Due to the nature of blockchains, there is a probability for a block added to the chain to be rolled back, which removes the block from the chain. This probability decreases as the block receives confirmations on top of it (i.e. new blocks are added after it). It is because of this rollback probability that `PRISM nodes` only process Cardano transactions after they receive a `SECURE_DEPTH` of confirmations, making the probability of rollbacks negligible in practical terms. Therefore, the protocol assumes a rollback free environment. 568 | 569 | Below, we describe how `PRISM nodes` process Cardano blocks, `AtalaObject`s, `AtalaBlock`s and operations. 570 | 571 | ### Processing of Cardano blocks 572 | 573 | `PRISM nodes` read the Cardano blockchain from the `GENESIS_PRISM_BLOCK`. Once a Cardano block reaches a `SERCURE_DEPTH` of confirmations, `PRISM nodes` explore each transaction of the block in order looking for encoded `AtalaObject`s. The encoding format MUST follow the one described in Appendix A, and there MUST be only one `AtalaObject` encoded per transaction. 574 | 575 | ### Processing of Atala objects and blocks 576 | 577 | As they evaluate confirmed transactions, nodes extract the `AtalaObject` messages from metadata (when found), and process them in the order they are found. The nodes MUST validate that the `AtalaBlock` inside it is not empty. If it is empty, the object is ignored. 578 | 579 | Once the object is validated, each `PRISM node` will proceed to inspect the operations inside the `AtalaBlock`. Operations MUST be processed in the order they have in the `AtalaBlock`. If an operation fails the validation rules, then the operation MUST be ignored, and the nodes MUST proceed with the next operations (if any) in the `AtalaBlock`. 580 | 581 | ### PRISM Nodes internal map 582 | 583 | When `PRISM nodes` process operations, they update an internal map of information. 584 | The map takes DIDs, and maps them to: 585 | - a hash that represents the last operation that affected the DID 586 | - a list of strings representing the context auxiliary information 587 | - keys' information 588 | - services' information 589 | 590 | The keys and services information consist of: 591 | - the list of keys associated to the DID. 592 | - Each key has additional timestamps and transaction ids (from the underlying Cardano blockchain) that describe when the keys were added or deleted. 593 | - the services associated to the DID. For each service we also have 594 | - the list of `type`s values with timestamps and transaction ids that declares when each type value has been added or deleted. 595 | - the list of `service_endpoint` values, with timestamps and transaction ids that declare when the values were added or deleted. 596 | 597 | For example, when a DID is created, an entry is added to the map, that adds the DID and maps it to the initial keys and services described in the corresponding `CreateDIDOperation`. This is: 598 | - it adds the list of keys and set their timestamp indicating when they were added on, it also set to each key the transaction id of the transaction that carried the said operation and 599 | - for each service it adds the singleton lists with `type` and `service_endpoint` values with a corresponding "added on" timestamp and transaction id to each entity. 600 | - if there is a non-empty list in `context`, it also associates it to the DID. 601 | - it also associates the hash of the `AtalaOperation` that wrapped the corresponding `CreateDIDOperation` to the DID. 602 | 603 | In the next sections, we will describe the structure of these timestamps, and the precise effect each operation induces in this map. 604 | 605 | 606 | #### Definition of time 607 | 608 | Each Cardano block has an associated timestamp attached to it which describes the real world time. All transactions inside a Cardano block share the same timestamp. Each `SignedAtalaOperation` is located inside an `AtalaBlock` which is attached to a Cardano transaction. We define the timestamp of an operation as the tuple _(cbt, absn, osn)_ where 609 | - `cbt` (*C*ardano *B*lock *T*ime) is the timestamp of the Cardano block that contains the transaction that carries the operation 610 | - `absn` (*A*tala *B*lock *S*equence *N*umber) is the position of the Cardano transaction that carries the `AtalaBlock` in the Cardano block 611 | - `osn` (*O*peration *S*equence *N*umber) is the position of the operation in the `AtalaBlock` that carries it 612 | 613 | every DID operation will have an associated timestamp (and transaction id), which `PRISM nodes` will store in their internal state while processing the operations. 614 | 615 | NOTE: in order to simplify the reading, from this point on, whenever we write that a timestamp is set/added/assigned, we also implicitly estate that the corresponding transaction id that carries the operation in context is also set/added/assigned accordingly. We recall that all operations carried by the same `AtalaBlock` will share a common transaction id. 616 | 617 | ### Processing of CreateDIDOperations 618 | 619 | Once extracted from an `AtalaBlock`, if a `PRISM node` finds a `SignedAtalaOperation` that contains a `CreateDIDOperation`, then: 620 | - The `SignedAtalaOperation` MUST be well constructed (as defined in previous sections) 621 | - The value in `signature` MUST match a signature of the operation in `operation` performed by the `signed_with` key present in the `public_keys` list defined in the operation. `signed_with` MUST refer to a key of `usage` `MASTER_KEY` 622 | - The corresponding DID MUST not already be registered in the `PRISM node` internal map 623 | - The `id`s of the public keys listed in `public_keys` MUST be different from each other 624 | - The `id`s of the services listed in `services` MUST be different from each other 625 | - If any of the above checks fail, the operation is ignored and no changes are made to the node map 626 | 627 | 628 | **Update of internal map** 629 | 630 | The `PRISM node` will add to its map, the DID derived from the create operation and associate to it: 631 | - the list of `PublicKey`s, attaching to each of them the corresponding operation timestamp as their addition timestamp. 632 | - the list of `Service`s, for each service 633 | - it adds a singleton list with the type with the corresponding operation timestamp as it addition timestamp attach to it. 634 | - it adds a singleton list with the service endpoint value with the corresponding operation timestamp as it addition timestamp attach to it. 635 | - if `context` is not empty, it associates to this DID the value of `context` as auxiliary data for the resolver. 636 | - It will also associate to this DID the hash of the `AtalaOperation` that contains the `CreateDIDOperation`. We will refer to this hash as its last operation hash. 637 | 638 | 639 | ### Processing of UpdateDIDOperations 640 | 641 | Once extracted from an `AtalaBlock`, if a `PRISM node` finds a `SignedAtalaOperation` that contains a `UpdateDIDOperation`, then: 642 | - The `SignedAtalaOperation` MUST be well constructed (as defined in previous sections) 643 | - The value in `signature` MUST match a signature of the operation in `operation` performed by the `signed_with` key. The key MUST be of `MASTER_KEY` usage, and be present in the `public_keys` list associated to the DID to update in the node internal map. The key MUST not have a revocation timestamp associated to it. 644 | - In the node map, the DID corresponding to the `id` of the update operation MUST have the value of `previous_operation_hash` as its associated last operation hash. 645 | - The update operation contains a list of update actions. 646 | - `PRISM nodes` MUST process all actions in order (see details below). 647 | - If any action fails to be processed, then the entire operation MUST be ignored. This means that any previous successful action from the list that may have been applied, MUST be rolled back 648 | - After applying the effect of all the actions of the update operation: 649 | - There MUST be at least one public key associated to the updated DID in the map, that has `MASTER_KEY` as usage and has no revocation time associated to it. 650 | - There MUST not be more than `MAX_SERVICE_NUMBER` services associated to the updated DID in the map where all have no revocation time associated to them. This means that there must not be more than `MAX_SERVICE_NUMBER` active services after the update. 651 | - There MUST not be more than `MAX_VERIFICATION_METHOD_NUMBER` keys associated to the updated DID in the map where all have no revocation time associated to them. This means that there must not be more than `MAX_VERIFICATION_METHOD_NUMBER` active keys after the update. 652 | - If any of the previous conditions is not met, the actions MUST be rolled back and the entire update operation is ignored. 653 | 654 | Each action will affect the information in the internal map, if all the actions are applied successfully then the last operation hash MUST be updated to the hash of the `AtalaOperation` that wrapped the corresponding `UpdateDIDOperation` 655 | 656 | Lets describe how to update the map of DIDs to their state based on each update action type 657 | 658 | #### Process AddKeyAction 659 | 660 | - The `id` of the `PublicKey` to add MUST NOT already be associated to the DID to update in the map 661 | 662 | **Update of internal map** 663 | 664 | The node updates the map, and adds to the corresponding DID the new key on the list of `PublicKeys`, attaching to the key the operation timestamp as addition time. 665 | 666 | #### Process RemoveKeyAction 667 | 668 | - The `keyId` of the action MUST already be associated to the DID to update in the map, this key MUST NOT have already an associated revocation time. 669 | 670 | **Update of internal map** 671 | 672 | The node updates the map, and adds to the key of the corresponding DID, the operation timestamp as its revocation time. 673 | 674 | 675 | #### Process AddServiceAction 676 | 677 | - The `id` of the `Service` to add MUST NOT already be associated to the DID to update in the map 678 | 679 | **Update of internal map** 680 | 681 | The node updates the map, and adds to the corresponding DID the new service, attaching to its lists of type and service endpoints the operation timestamp as addition time. 682 | 683 | #### Process RemoveServiceAction 684 | 685 | - The `serviceId` of the action MUST already be associated to the DID to update in the map, this service MUST NOT have already an associated deletion time. 686 | 687 | **Update of internal map** 688 | 689 | The node updates the map, and adds to the service of the corresponding DID, and for the currently active `type` and list of `service_endpoints` the operation timestamp as their deletion times. 690 | 691 | #### Process UpdateServiceAction 692 | 693 | - The `serviceId` of the action MUST already be associated to the DID to update in the map, 694 | - this service will have a list of types, there MUST be exactly one type in this list that does not have already an associated deletion time. We will call this type `current_type` 695 | - this service will have a list of service endpoints, there MUST be exactly one service endpoint that does not have already an associated revocation time. We will call this list `current_endpoints` 696 | 697 | **Update of internal map** 698 | - If the `UpdateServiceAction` has a non empty `type`, we will refer to it as `new_type` 699 | - The node adds the operation timestamp to `current_type` as its deletion time, and adds `new_type` to the corresponding list of types with the operation timestamp as its addition timestamp. 700 | - If the `UpdateServiceAction` has a non empty `service_endpoints`, we will refer to it as `new_services` 701 | - The node adds the operation timestamp to `current_endpoints` as its revocation time, and adds `new_endpoints` to the corresponding list of service endpoints with the operation timestamp as its addition timestamp. 702 | 703 | #### Processing PatchContextAction 704 | 705 | If the DID to update has an empty context associated to it in the map: 706 | - the field `context` MUST NOT be empty and MUST NOT contain repeated values 707 | 708 | **Update of the internal map** 709 | - If `context` is empty, the DID removes the previous context list associated to it. 710 | - It `context` is not empty, the DID replaces the old list for the new one on its map. 711 | 712 | ### Processing of DeactivateDIDOperations 713 | 714 | Once extracted from an `AtalaBlock`, if a `PRISM node` finds a `SignedAtalaOperation` that contains a `DeactivateDIDOperation`, then: 715 | - The `SignedAtalaOperation` MUST be well constructed (as defined in previous sections) 716 | - The value in `signature` MUST match a signature of the operation in `operation` performed by the `signed_with` key. The key MUST be of `MASTER_KEY` usage, and present in the `public_keys` list associated to the DID to update in the node internal map. The key MUST not have a deletion timestamp associated to it. 717 | - In the node map, the DID corresponding to the `id` of the update operation MUST have the value of `previous_operation_hash` as its associated last operation hash. 718 | - If any of the above checks fail, the operation is ignored and no changes are made to the node map 719 | 720 | 721 | **Update of internal map** 722 | - For all keys and services that do not have a revocation/deletion timestamp, nodes MUST set the deactivation operation timestamp as the deletion timestamp. 723 | - The new last operation hash MUST be the hash of the `AtalaOperation` that wrapped the corresponding `DeactivateDIDOperation` 724 | 725 | Note that this marks all keys (including `MASTER_KEY` keys) as deleted. Meaning that no further updates will be possible on the corresponding DID. 726 | 727 | ### Processing of ProtocolVersionUpdateOperations 728 | 729 | Once extracted from an `AtalaBlock`, if a `PRISM node` finds a `SignedAtalaOperation` that contains a `ProtocolVersionUpdateOperation`, then: 730 | - The `SignedAtalaOperation` MUST be well constructed (as defined in previous sections) 731 | - The value in `signature` MUST match a signature of the operation in `operation` performed by the `signed_with` key. The key MUST be of `MASTER_KEY` usage, and present in the `public_keys` list associated to the `SYSTEM_UPDATE_DID` in the node internal map. The key MUST not have a revocation timestamp associated to it. 732 | - The `effective_since` MUST be positive. 733 | - The new version MUST: 734 | - Either increase the `major_version` of the protocol, or keep the `major_version` unchanged while increasing the `minor_version`. If the version change is different, the operation is rejected 735 | - If any of the above checks fail, the operation is ignored and no changes are made to the node map 736 | 737 | 738 | The purpose of this operation is to announce when the new version will become effective. The internal state map will only change if the new rules of the protocol establishes so after the version becomes effective. This is something that the new version of the protocol would define. 739 | 740 | 741 | ## Translation of PRISM nodes states to DID Documents 742 | 743 | In this section, we will refine the DID resolution process. 744 | Given the DID `d` that a user is resolving: 745 | 746 | - If `d` is a short form DID` 747 | - Any `PRISM node` will look in its internal map in search for `d` 748 | - If `d` is not found, the node returns a `notFound` error 749 | - If `d` is found, the node will return to the user the content of the map associated to `d`. This is, the list of keys and services information associated to `d` with all the timestamp information. If non-empty, it will also return the list of `context` strings associated to the DID. 750 | - If `d` is in long form, `PRISM nodes` MUST: 751 | - extract the short form DID from the DID 752 | - validate the short form DID correspondence (i.e. the encoded operation must match the expected hash), and validates that the decoded operation is a well constructed `AtalaOperation` that contains a `CreateDIDOperation` 753 | - If the validations fail, it returns an `invalidDid` error. 754 | - check if there is information about the short form DID in the internal map. 755 | - If there is, it returns the data of the map as in the case of `d` being a short form 756 | - If there is no information: 757 | - it decodes the `AtalaOperation` from the long form DID, 758 | - run the validations described for a `CreateDIDOperation` 759 | - if validations fail, the node will return an `invalidDid` error 760 | - if the validations are successful, the node returns the information that would be generated in its internal map if the node would process the effect of the `CreateDIDOperation` with the difference that there would add no timestamp information (this is because the decoded operation has no associated timestamp) 761 | 762 | In order to transform this returned information to a DID document, we do as follows. 763 | - From the list of public keys' information, extract the keys that have no revocation timestamps associated to them. 764 | - From the list of services' information: 765 | - take the services' id that have exactly one type and service endpoint's string with no deletion data attach to them 766 | - construct a list of services that have the mentioned `id`s as `id` and add as `type` and `service_endpoint`, the corresponding only elements that have no deletion time associated to them. 767 | This leaves us with a list of active keys, and a list of active services. 768 | - If we obtain a non-empty list of contexts, we keep it without changes 769 | 770 | ### Constructing a JSON-LD DID document 771 | 772 | If the services' and keys' lists obtained in the previous section are empty, we have that the DID has been deactivated, and we can return an empty JSON object. 773 | 774 | NOTE: The protocol rules guarantee that if the list of keys is empty, then the list of services must be empty. If this is not the case, then this indicates an implementation error. 775 | 776 | If the list of keys is not empty, then we construct the DID document as follows: 777 | - the top level `id` and `controller` are the DID received for resolution, `d` 778 | - the `@context` of the generated document MUST include at a minimum 779 | ``` 780 | [ 781 | "https://www.w3.org/ns/did/v1" 782 | ] 783 | ``` 784 | - Depending on the verification method types and services types in the DID document, additional entries MUST be added to the `@context` after "https://www.w3.org/ns/did/v1", such as: 785 | - For verification method type `"JsonWebKey2020"`, add `"https://w3id.org/security/suites/jws-2020/v1"` 786 | - For service type `"DIDCommMessaging"`, add `"https://didcomm.org/messaging/contexts/v2"` 787 | - For service type `"LinkedDomains"`, add "https://identity.foundation/.well-known/did-configuration/v1" 788 | - If the user added more services' types not covered in the above list, he is responsible for adding their corresponding contexts in the `context` list. If there is a list of context strings associated to the DID, the resolver MUST append it to the end of the `@context` constructed so far. 789 | - For each key that does not have usage `MASTER_KEY` nor `REVOCATION_KEY`, we create an object in the `verificationMethod` field. For each object: 790 | - The `id` is the key id prepended by the DID received as input and a `#` character separating the strings. For instance, for an identifier `key-1`, and `d` equals to `did:prism:abs` we will obtain the `id` equal to `did:prism:abs#key-1` 791 | - If the `curve` field value associated to the key is `SECP256K1_CURVE_NAME` 792 | - The `type` is "JsonWebKey2020" 793 | - The `controller` is the DID received for resolution, `d` 794 | - The `publicKeyJwk` is the JWK public key where: 795 | - `crv` is "secp256k1" 796 | - `kty` is "EC" 797 | - `x` and `y` are the corresponding coordinates of the key 798 | - If `curve` field value associated to the key is `ED25519_CURVE_NAME` 799 | - The `type` is "JsonWebKey2020" 800 | - The `controller` is the DID received for resolution, `d` 801 | - The `publicKeyJwk` is the JWK public key where: 802 | - `kty` is "OKP" 803 | - `crv` is "Ed25519" 804 | - `x` is derived from the coordinates of the key 805 | - If `curve` field value associated to the key is `X25519_CURVE_NAME` 806 | - The `type` is "JsonWebKey2020" 807 | - The `controller` is the DID received for resolution, `d` 808 | - The `publicKeyJwk` is the JWK public key where: 809 | - `kty` is "OKP" 810 | - `crv` is "X25519" 811 | - `x` is derived from the coordinates of the key 812 | - for each verification relationship, if there is a key usage matching to it, the verification relationship will make a reference by `id` to the respective key in `verificationMethod` 813 | - for each active service, the translation is trivial as the fields have a one on one correspondence to the W3C data model 814 | - The `id` of each service do follow the same rule as `id`s of verification methods. This is, the translation adds the DID to resolve as prefix to the id known by `PRISM nodes` and use a `#` character to separate the strings 815 | 816 | ### Constructing the DID document metadata 817 | 818 | In addition to the DID document, the DID resolution process also returns DID document metadata. 819 | 820 | - If `d` is in long form and has been published, DID document metadata MUST contain a `canonicalId` property with the short form DID as its value. 821 | - If `d` is in short form or has not been published, DID document metadata MUST NOT contain a `canonicalId` property. 822 | - DID document metadata MUST contain a `created` property with the timestamp of the Cardano block that contained the first valid `SignedAtalaOperation` with a `CreateDIDOperation` that created the DID. 823 | - DID document metadata MUST contain an `updated` property with the timestamp of the Cardano block that contained the latest valid `SignedAtalaOperation` that changed the DID's internal state. 824 | - DID document metadata MUST contain a `versionId` property with the hash of the `AtalaOperation` contained in the latest valid `SignedAtalaOperation` that created the DID or changed the DID's internal state. 825 | - DID document metadata MUST contain a `deactivated` property with the boolean value `true` if the DID has been deactivated with a valid `DeactivateDIDOperation`. Otherwise, this property is OPTIONAL, but if included, MUST have the boolean value `false`. 826 | - Resolvers MAY add additional method-specific DID document metadata properties, such as 827 | - `cardanoTransactionPosition`: The position of the Cardano transaction in the Cardano block (i.e. Atala block sequence number) 828 | - `operationPosition`: The position of the `SignedAtalaOperation` in the `AtalaBlock` (i.e. the operation sequence number) 829 | - `originTxId`: The Cardano transaction id of the valid `SignedAtalaOperation` that carried the `CreateDIDOperation` that anchored the DID on-chain (if published) 830 | - `updatedTxId`: The Cardano transaction id of the latest operation that changed the DID's internal state 831 | - `deactivatedTxId`: Similar for deactivation 832 | 833 | Example: 834 | 835 | ```json 836 | { 837 | "didDocumentMetadata": { 838 | "canonicalId": "did:prism:db47e78dd57d2043a7a704fbd9d186a586682110a2097ac06dbc83b35602f290", 839 | "created": "2023-02-04T13:52:10Z", 840 | "updated": "2023-01-18T02:19:25Z", 841 | "versionId": "588ae32c68fe6c2a3af1b076284b6d880a40888025e382ab39dc9dc7cd9de382", 842 | "deactivated": true, 843 | "cardanoTransactionPosition": 0, 844 | "operationPosition": 0 845 | } 846 | } 847 | ``` 848 | 849 | ## Security Considerations 850 | 851 | In this section, we will include adequate comments about the common security aspects around DID methods, and how they apply to the `prism` method. 852 | 853 | ### Eavesdropping 854 | 855 | The `prism` method does not generate messages of private nature as part of its protocol. Users create operations only when they intend to submit them to the blockchain or, in the case of DID creation, when they intend to share the DID. Therefore, the `prism` DID method is secure against eavesdropping attacks. It is recommended to users, not to generate `prism` operations if they do not intend to make them public. 856 | 857 | ### Replay 858 | 859 | No operation in the `prism` protocol can suffer a replay attack by construction. `CreateDIDOperation`, once submitted, is associated to a DID. A second publication of the same operation will fail to be applied by `PRISM nodes`. For updates and deactivation, the corresponding operations contain a hash to the preceding operation, creating a linear hash chain. An operation replay would fail a previous hash check. 860 | For the case of protocol updates, the operation always increases the protocol version, making impossible to post twice the same operation. 861 | 862 | ### Message insertion 863 | 864 | All operations that are published in the `prism` protocol require a cryptographic signature. The protocol relays the protection against message insertion attacks to: 865 | 1. The security of the signature scheme 866 | 2. The security of communication channels 867 | 868 | Users should not create operations unless they plan to publish them. 869 | 870 | ### Deletion 871 | 872 | Protocol messages that are publicly shared, are published on the Cardano blockchain. 873 | Due to the nature of decentralized blockchains, a deletion attack would require the attacker to force the deletion of information from all the nodes in the Cardano chain. We consider this a reasonably hard condition to perform such an attack. 874 | 875 | ### Modification 876 | 877 | All messages in the `prism` protocol have an integrity guarantee. Creation operations have an integrity hash check that binds operations to the DID generated. All other operations are cryptographically signed. The method security hence relies in the security of the cryptographic scheme used. 878 | 879 | ### Denial of service 880 | 881 | In the setting where a user is not hosting his own `PRISM node`, the security relies on the security measures taken by the delegated party. 882 | Any user running a `PRISM node` should take traditional mitigations to these type of attacks. This includes but is not limited to proper resource provision to `PRISM node` deployments, load balancing techniques, rate limitations on queries, push-back to clients. 883 | 884 | ### Amplification 885 | 886 | There are three types of requests a `PRISM node` replies to: 887 | 1. Resolution queries 888 | 2. Operation submission queries (publish operations to the blockchain) 889 | 3. Processing new operations read from the blockchain 890 | 891 | Resolution and operation submission queries can be limited by traditional techniques such as rate limiters and load balancers. 892 | For the case of reading operations from the Cardano blockchain, an attacker would need to publish those operations first, and this requires fees to be paid. These fees impose a cost limit to perform attacks of this nature. Therefore, we rely partially on the underlying blockchain spam protection to avoid resource exhaustion. 893 | Additionally, the protocol limits the length of identifiers, URIs, and other strings inside operations; it also restricts a maximum number of verification methods and services associated to a DID Document. These limitations also contribute to maintain the consumption of resources bounded to reasonable limits. 894 | 895 | ### Man-in-the-middle 896 | 897 | The messages shared as part of the `prism` protocol do not have expected recipients, making man in the middle attacks non applicable. 898 | 899 | ### Other considerations 900 | 901 | #### Trust delegation settings 902 | 903 | We would like to add some comments in relation to the trust setting that the method allows to use. A user can opt to not self host a `PRISM node`. This situation generates a security dependency on the trusted `PRISM node` provider. For instance, a user could submit a DID update to the provider, but this actor could opt to not forward the operation to the blockchain. Furthermore, the node provider could lie to the user and convince him that the operation was indeed submitted and applied. 904 | 905 | In order to avoid these type of situations, we suggest the following recommendations: 906 | 1. If possible, self host an instance of the `PRISM node` 907 | 2. If self hosting is not possible, query a set of `PRISM nodes` provided by different non related actors 908 | 3. When an operation is submitted to a node, query about its confirmation to multiple providers 909 | 4. `PRISM nodes`, based on implementation, are able to return the transaction id of the underlying blockchain that carries the published operations. 910 | 911 | #### Blockchain funds safety 912 | 913 | `PRISM nodes` can submit transactions to their underlying operating blockchain. This implies that fees are paid to the blockchain when transactions are submitted. Such fees are paid by a wallet/account on the chain. When the wallet is managed by the `PRISM node`, proper access control to the node should be in place to avoid draining of the said funds. In particular, it is recommended to have minimal funds on the mentioned wallet, and refund it periodically as demand requires. 914 | 915 | #### Protocol update keys 916 | 917 | The owner of `SYSTEM_UPDATE_DID` should take the appropriate measures to protect such DID. 918 | These is, to protect the cryptographic keys that control the said DID. Some possible measures can include applying threshold techniques, which allow the decomposition of cryptographic secrets into pieces, which could be stored by separate actors. The reconstruction of the secrets could be done with access to a subset of all the initial pieces. 919 | 920 | #### Rollbacks 921 | 922 | The PRISM DID method, in practice, assumes that the underlying blockchain won't present a rollback that could affect operations once they have been processed. We would like to share some perspective to the implication of the `SECURE_DEPTH` value in the context of Cardano. According to the [Ouroboros Praos](https://eprint.iacr.org/2017/573.pdf) protocol, the current selection for `SECURE_DEPTH` implies that an attacker controlling ten percent of the total staked ADA of the Cardano network, would have a probability below 0.01 to rollback a block with `SECURE_DEPTH` depth or higher. This is, an attacker would need (at the time of this writing) to control around 2 billion ADA to have at best that probability to succeed on forcing a rollback for operations already processed by a PRISM node. The economic value of that amount of ADA reflects the resources required to perform a potential attack based on rollbacks. 923 | 924 | ## Privacy Considerations 925 | 926 | As most DID methods, it is recommended to users to not add personal identifiable information in their DID documents. This includes: 927 | - Verification method ids 928 | - Services ids 929 | - URIs 930 | - Any other place where it could fit inside DID Documents 931 | 932 | In addition, consider correlation risks associated to blockchain transactions. When a user self hosts a `PRISM node`, all his published events will be tied to blockchain transactions. The user should take adequate measures to avoid correlation of independent DIDs. Examples of mitigation strategies are: 933 | - Have multiple wallets for different DIDs 934 | - Use `PRISM nodes` hosted by external providers to publish operations 935 | 936 | We want to remind the reader that all published operations of the `prism` protocol are hard, if not impossible, to delete. 937 | 938 | ## References 939 | 940 | ## Appendix A: Cardano's metadata encoding 941 | 942 | [Cardano's metadata](https://developers.cardano.org/docs/transaction-metadata/) encodes a subset of JSON values. 943 | For the case of `prism` `AtalaObjects`, the top level value is a JSON object of the following shape: 944 | 945 | ```json 946 | { 947 | "21325" : { 948 | "map" : [ 949 | { 950 | "k" : { "string": "v" }, 951 | "v" : { "int" : 1 } 952 | }, 953 | { 954 | "k" : { "string" : "c" }, 955 | "v" : { 956 | "list" : group(atalaObject) 957 | } 958 | } 959 | ] 960 | } 961 | } 962 | ``` 963 | 964 | where `group` takes the `AtalaObject` message to encode and: 965 | 1. converts it to a byte array, 966 | 1. splits the array into sections of 64 bytes (the last section may contain less bytes) 967 | 1. returns a list of JSON objects (one object per section) of the form 968 | 969 | ```json 970 | { 971 | "bytes" : HEX_ENCODED_SEGMENT 972 | } 973 | ``` 974 | 975 | where `HEX_ENCODED_SEGMENT` is the hexadecimal encoding of the corresponding section of up to 64 bytes. 976 | 977 | NOTE: the value `21325` represents the last 16 bits of 344977920845, which is the decimal representation of the concatenation of the hexadecimals 50 52 49 53 4d that form the word PRISM in ASCII. 978 | 979 | ## Appendix B: Protobuf models 980 | 981 | In this section, we can find all the protobuf definitions referenced in previous sections 982 | 983 | ```protobuf 984 | 985 | /** 986 | * Represent a block that holds operations. 987 | */ 988 | message AtalaBlock { 989 | reserved 1; // Represents the version of the block. Deprecated 990 | repeated SignedAtalaOperation operations = 2; // A signed operation, necessary to post anything on the blockchain. 991 | } 992 | 993 | /** 994 | * Wraps an AtalaBlock and its metadata. 995 | */ 996 | message AtalaObject { 997 | reserved 1, 2, 3; 998 | reserved "block_hash"; 999 | reserved "block_operation_count"; // Number of operations in the block. 1000 | reserved "block_byte_length"; // Byte length of the block. 1001 | 1002 | AtalaBlock block_content = 4; // The block content. 1003 | } 1004 | 1005 | // Every key has a single purpose: 1006 | enum KeyUsage { 1007 | // UNKNOWN_KEY is an invalid value - Protobuf uses 0 if no value is provided and we want the user to explicitly choose the usage. 1008 | UNKNOWN_KEY = 0; 1009 | MASTER_KEY = 1; 1010 | ISSUING_KEY = 2; 1011 | KEY_AGREEMENT_KEY = 3; 1012 | AUTHENTICATION_KEY = 4; 1013 | REVOCATION_KEY = 5; 1014 | CAPABILITY_INVOCATION_KEY = 6; 1015 | CAPABILITY_DELEGATION_KEY = 7; 1016 | } 1017 | 1018 | /** 1019 | * Holds the necessary data to recover an Elliptic Curve (EC)'s public key. 1020 | */ 1021 | message ECKeyData { 1022 | string curve = 1; // The curve name, like secp256k1. 1023 | bytes x = 2; // The x coordinate, represented as bytes. 1024 | bytes y = 3; // The y coordinate, represented as bytes. 1025 | } 1026 | 1027 | /** 1028 | * Holds the compressed representation of data needed to recover Elliptic Curve (EC)'s public key. 1029 | */ 1030 | message CompressedECKeyData { 1031 | string curve = 1; // The curve name, like secp256k1. 1032 | bytes data = 2; // compressed Elliptic Curve (EC) public key data. 1033 | } 1034 | 1035 | /** 1036 | * Represents a public key with metadata, necessary for a DID document. 1037 | */ 1038 | message PublicKey { 1039 | reserved 3, 4, 5, 6; 1040 | string id = 1; // The key identifier within the DID Document. 1041 | KeyUsage usage = 2; // The key's purpose. 1042 | 1043 | // The key's representation. 1044 | oneof key_data { 1045 | ECKeyData ec_key_data = 8; // The Elliptic Curve (EC) key. 1046 | CompressedECKeyData compressed_ec_key_data = 9; // Compressed Elliptic Curve (EC) key. 1047 | }; 1048 | } 1049 | 1050 | 1051 | // The operation to create a public DID. 1052 | message CreateDIDOperation { 1053 | DIDCreationData did_data = 1; // DIDCreationData with public keys and services 1054 | 1055 | // The data necessary to create a DID. 1056 | message DIDCreationData { 1057 | reserved 1; // Removed DID id field which is empty on creation 1058 | repeated PublicKey public_keys = 2; // The keys that belong to this DID Document. 1059 | repeated Service services = 3; // The list of services that belong to this DID Document. 1060 | repeated string context = 4; // The list of @context values to consider on JSON-LD representations 1061 | } 1062 | } 1063 | 1064 | // The necessary data to add a key to a DID. 1065 | message AddKeyAction { 1066 | PublicKey key = 1; // The key to include. 1067 | } 1068 | 1069 | // The necessary data to remove a key from a DID. 1070 | message RemoveKeyAction { 1071 | string keyId = 1; // the key id to remove 1072 | } 1073 | 1074 | message AddServiceAction { 1075 | Service service = 1; 1076 | } 1077 | 1078 | message RemoveServiceAction { 1079 | string serviceId = 1; 1080 | } 1081 | 1082 | message UpdateServiceAction { 1083 | string serviceId = 1; // scoped to the did, unique per did 1084 | string type = 2; // new type if provided 1085 | string service_endpoints = 3; 1086 | } 1087 | 1088 | message PatchContextAction { 1089 | repeated string context = 1; // The list of strings to use by resolvers during resolution when producing a JSON-LD output 1090 | } 1091 | 1092 | // The potential details that can be updated in a DID. 1093 | message UpdateDIDAction { 1094 | 1095 | // The action to perform. 1096 | oneof action { 1097 | AddKeyAction add_key = 1; // Used to add a new key to the DID. 1098 | RemoveKeyAction remove_key = 2; // Used to remove a key from the DID. 1099 | AddServiceAction add_service = 3; // Used to add a new service to a DID, 1100 | RemoveServiceAction remove_service = 4; // Used to remove an existing service from a DID, 1101 | UpdateServiceAction update_service = 5; // Used to Update a list of service endpoints of a given service on a given DID. 1102 | PatchContextAction patch_context = 6; // Used to Update a list of `@context` strings used during resolution for a given DID. 1103 | } 1104 | } 1105 | 1106 | // Specifies the necessary data to update a public DID. 1107 | message UpdateDIDOperation { 1108 | bytes previous_operation_hash = 1; // The hash of the most recent operation that was used to create or update the DID. 1109 | string id = 2; // @exclude TODO: To be redefined after we start using this operation. 1110 | repeated UpdateDIDAction actions = 3; // The actual updates to perform on the DID. 1111 | } 1112 | 1113 | // Specifies the protocol version update 1114 | message ProtocolVersionUpdateOperation { 1115 | string proposer_did = 1; // The DID suffix that proposes the protocol update. 1116 | ProtocolVersionInfo version = 2; // Information of the new version 1117 | } 1118 | 1119 | 1120 | message ProtocolVersion { 1121 | // Represent the major version 1122 | int32 major_version = 1; 1123 | // Represent the minor version 1124 | int32 minor_version = 2; 1125 | } 1126 | 1127 | message ProtocolVersionInfo { 1128 | reserved 2, 3; 1129 | string version_name = 1; // (optional) name of the version 1130 | int32 effective_since = 4; // Cardano block number that tells since which block the update is enforced 1131 | 1132 | // New major and minor version to be announced, 1133 | // If major value changes, the node MUST stop issuing and reading operations, and upgrade before `effective_since` because the new protocol version. 1134 | // If minor value changes, the node can opt to not update. All events _published_ by this node would be also 1135 | // understood by other nodes with the same major version. However, there may be new events that this node won't _read_ 1136 | ProtocolVersion protocol_version = 5; 1137 | } 1138 | 1139 | message DeactivateDIDOperation { 1140 | bytes previous_operation_hash = 1; // The hash of the most recent operation that was used to create or update the DID. 1141 | string id = 2; // DID Suffix of the DID to be deactivated 1142 | } 1143 | 1144 | // The possible operations affecting the blockchain. 1145 | message AtalaOperation { 1146 | reserved 3, 4; // fields used by an extension of the protocol. Not relevant for the DID method 1147 | // The actual operation. 1148 | oneof operation { 1149 | // Used to create a public DID. 1150 | CreateDIDOperation create_did = 1; 1151 | 1152 | // Used to update an existing public DID. 1153 | UpdateDIDOperation update_did = 2; 1154 | 1155 | // Used to announce new protocol update 1156 | ProtocolVersionUpdateOperation protocol_version_update = 5; 1157 | 1158 | // Used to deactivate DID 1159 | DeactivateDIDOperation deactivate_did = 6; 1160 | }; 1161 | } 1162 | 1163 | // A signed operation, necessary to post anything on the blockchain. 1164 | message SignedAtalaOperation { 1165 | string signed_with = 1; // The key ID used to sign the operation, it must belong to the DID that signs the operation. 1166 | bytes signature = 2; // The actual signature. 1167 | AtalaOperation operation = 3; // The operation that was signed. 1168 | } 1169 | 1170 | message Service { 1171 | string id = 1; 1172 | string type = 2; 1173 | string service_endpoint = 3; 1174 | } 1175 | ``` 1176 | --------------------------------------------------------------------------------