- Introduction
- Specifications
- CorDapp design -CMN views
- Setup
- Caveats
- Risks/Known Attack Surface
- Contribution guidelines
- Partnership
This repository contains all components necessary to provide a Corda ‘Decentralized Identifier Method’ within the meaning of the Data Model and Syntaxes for Decentralized Identifiers Draft Community Group Report 06 February 2019.
The system architecture outlined below illustrates the high level components without going into implementation details. On a high level, persistence of DID documents will be provided by a consortium of trusted nodes operating within a network.
End users that aim to create, read, update or delete DID documents can do so by interacting with a trusted node of their choosing. The API provided for interaction is exposing REST endpoints over HTTP, using a JSON based envelope format closely aligned with the JSON-LD examples found in the draft community report.
When users interact with consortium member nodes, their requests will be handled by a web server that transforms the requests into a format suitable for Corda. The web server component is running in a process independent of Corda. User calls ‘proxied’ through this way will invoke a Flow on one of the consortium nodes. As part of this flow, consortium nodes will validate that the id provided by the user is valid and that the message has cryptographic integrity (i.e. that the DID document is signed properly). Once this validation was successful, DID documents will be replicated from the trusted node (i.e. the node the user chose to interact with via REST) to all witness nodes (i.e. all other nodes in the consortium). Witness nodes will perform a cryptographic integrity check as part of the contract underpinning this transaction.
Once replicated, anyone with access to one of the consortium nodes can request the DID document by querying the REST API of an arbitrary node for the document ID.
A Corda DID specifies the corda
method, a target network (for e.g. testnet
, tcn-uat
, tcn
) and a UUID formatted as per RFC 4122.
did:corda:([a-z]+(?:\-?[a-z]+)*):([0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12})
I.e.
did:corda:testnet:3df6b0a1-6b02-4053-8900-8c36b6d35fa1
did:corda:tcn:3df6b0a1-6b02-4053-8900-8c36b6d35fa1
did:corda:tcn-uat:3df6b0a1-6b02-4053-8900-8c36b6d35fa1
did:corda:persistent-private:3df6b0a1-6b02-4053-8900-8c36b6d35fa1
Initially, consortium membership is envisioned to change rarely so that a fixed set of member nodes can be defined and provided to consortium members. A more dynamic approach to membership may be developed later.
ID | Network | Stage | Consortium Member Nodes |
---|---|---|---|
testnet |
Testnet | -- | --to be defined-- |
tcn-uat |
The Corda Network | UAT | --to be defined-- |
tcn |
The Corda Network | Live | --to be defined-- |
Users may choose to deploy DID Methods on a custom network. The node will have to specify the type of network in cordapp-config file.
Corda DID supports CRUD operations. Developers can perform CRUD operations in two ways:
- REST APIs
- Flows
Corda DID REST APIs (did-api
)
The DID API is the server component to be deployed by consortium member nodes in conjunction with the CorDapp. It provides Method specific APIs to create, read, update or delete DID documents.
The Corda DID method achieves proof-of-ownership of a document by requiring proof-of-ownership of the keys contained in the document.
To implement that, any DID document must be wrapped in an envelope. This envelope must contain signatures by all private keys associated with the public keys contained in the documents.
Envelopes that do not contain signatures for all public keys will be rejected. Envelopes using unsupported cryptographic suites or unsupported serialization mechanisms will be rejected. In the current implementation there are severe restrictions on which suites and serialisation mechanisms can be used (see Caveats below).
Instruction data tells the API what to do with the document received. It also contains proof of ownership of keys. The instruction data is to be formatted according to the following schema:
{
"definitions": {},
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": [
"action",
"signatures"
],
"properties": {
"action": {
"$id": "#/properties/action",
"type": "string",
"enum": [
"create",
"read",
"update",
"delete"
]
},
"signatures": {
"$id": "#/properties/signatures",
"type": "array",
"items": {
"$id": "#/properties/signatures/items",
"type": "object",
"required": [
"id",
"type",
"signatureBase58"
],
"properties": {
"id": {
"$id": "#/properties/signatures/items/properties/id",
"type": "string",
"description": "The ID of the public key that is part of the key pair signing the document.",
"examples": [
"did:corda:testnet:3df6b0a1-6b02-4053-8900-8c36b6d35fa1#keys-1",
"did:corda:tcn:3df6b0a1-6b02-4053-8900-8c36b6d35fa1#keys-2"
],
"pattern": "^did:corda:(testnet|tcn-uat|tcn):[0-9a-f]{8}\\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\\b[0-9a-f]{12}#.+$"
},
"type": {
"$id": "#/properties/signatures/items/properties/type",
"type": "string",
"description": "The cryptographic suite this key has been generated with. More formats (RsaSignature2018, EdDsaSASignatureSecp256k1) to follow.",
"enum": [
"Ed25519Signature2018"
]
},
"signatureBase58": {
"$id": "#/properties/signatures/items/properties/signatureBase58",
"type": "string",
"description": "The binary signature in Base58 representation. More formats to follow.",
"examples": [
"54CnhKVqE63rMAeM1b8CyQjL4c8teS1DoyTfZnKXRvEEGWK81YA6BAgQHRah4z1VV4aJpd2iRHCrPoNTxGXBBoFw"
]
}
}
}
}
}
}
Encoding | Description |
---|---|
signatureHex |
Hex encoded signature. |
signatureBase64 |
Base64 encoded signature. |
signatureBase58 |
Base58 encoded signature. |
signatureMultibase |
Multibase encoded signature,details here. |
{
"action": "create",
"signatures": [
{
"id": "did:corda:tcn:d51924e1-66bb-4971-ab62-ec4910a1fb98#keys-1",
"type": "Ed25519Signature2018",
"signatureBase58": "54CnhKVqE63rMAeM1b8CyQjL4c8teS1DoyTfZnKXRvEEGWK81YA6BAgQHRah4z1VV4aJpd2iRHCrPoNTxGXBBoFw"
}
]
}
{
"action": "create",
"signatures": [
{
"id": "did:corda:tcn:84602311-bd95-4006-968c-01a69d035d64#keys-1",
"type": "Ed25519Signature2018",
"signatureMultibase": "bb3i4jlob2pomlx5yjv5adir7r26tkor6iqroosojkqi4wq2kcjtiju3moxsrkwmobhtlega27uzuxtncks6yib6otqybfykjyzieqe"
}
]
}
{
"action": "create",
"signatures": [
{
"id": "did:corda:tcn:03d7411f-ae67-4c89-94b8-de802f017745#keys-1",
"type": "Ed25519Signature2018",
"signatureHex": "04242D453FA6191B67308B9454E08EC2D59524063F53F85D11E43151283DF672959B9F546CD437FACA914DD15D41F7F6B5FF0AA00ABF5EE91826C70EA83F0E03"
}
]
}
{
"action": "create",
"signatures": [
{
"id": "did:corda:tcn:4b78d87e-1dee-403d-89d6-d2e12926d309#keys-1",
"type": "Ed25519Signature2018",
"signatureBase64": "Kh39kEoMvzfolBimiT/6wGeTys5Leuk/M0im9CligIpRXsJnIx4STphsofZBnbX198H7AfuVp8IJYyzMwKtaAg=="
}
]
}
The format of the document follows the Data Model and Syntaxes for Decentralized Identifiers Draft Community Group Report 06 February 2019 in JSON-LD.
Encoding | Description |
---|---|
publicKeyHex |
Hex encoded public key. |
publicKeyBase64 |
Base64 encoded public key. |
publicKeyBase58 |
Base58 encoded public key. |
publicKeyMultibase |
Multibase encoded public key,details here. |
publicKeyPem |
pem encoded public key. |
publicKeyJwk |
JWK encoded public key,details here |
{
"@context": "https://w3id.org/did/v1",
"id": "did:corda:tcn:2ce7cef6-6948-4d71-a6ab-dbd8096050fb",
"publicKey": [
{
"id": "did:corda:tcn:2ce7cef6-6948-4d71-a6ab-dbd8096050fb#keys-2",
"type": "EcdsaVerificationKeySecp256k1",
"controller": "did:corda:tcn:2ce7cef6-6948-4d71-a6ab-dbd8096050fb",
"publicKeyBase58": "PZ8Tyr4Nx8MHsRAGMpZmZ6TWY63dXWSCzamP7YTHkZc78MJgqWsAyXmwFufbxxH7JTxJDYFaUzCG9xjsk3gBEYmAFNcr7Y2FckVN65SuiU8YYGBWPgBXc8xs"
}
]
}
{
"@context": "https://w3id.org/did/v1",
"id": "did:corda:tcn:4b96fabe-bb6c-49ee-9da1-cc40cfec3c40",
"publicKey": [
{
"id": "did:corda:tcn:4b96fabe-bb6c-49ee-9da1-cc40cfec3c40#keys-1",
"type": "Ed25519VerificationKey2018",
"controller": "did:corda:tcn:4b96fabe-bb6c-49ee-9da1-cc40cfec3c40",
"publicKeyBase64": "MCowBQYDK2VwAyEAQF9YKxvr6GMdZ6l/1dHCY6fa74WAE5qO87k+fh1Dj6s="
}
]
}
{
"@context": "https://w3id.org/did/v1",
"id": "did:corda:tcn:ac157dc3-081f-4b34-997d-a11a533a6776",
"publicKey": [
{
"id": "did:corda:tcn:ac157dc3-081f-4b34-997d-a11a533a6776#keys-1",
"type": "Ed25519VerificationKey2018",
"controller": "did:corda:tcn:ac157dc3-081f-4b34-997d-a11a533a6776",
"publicKeyHex": "302A300506032B657003210017FF237F722D21FA8C7B89EB0FFD415086D7A68A0F6AD9EF221CA9BABEC2A8BA"
}
]
}
{
"@context": "https://w3id.org/did/v1",
"id": "did:corda:tcn:f350302c-de93-4eeb-b32c-c1f0ab7366d0",
"publicKey": [
{
"id": "did:corda:tcn:f350302c-de93-4eeb-b32c-c1f0ab7366d0#keys-1",
"type": "Ed25519VerificationKey2018",
"controller": "did:corda:tcn:f350302c-de93-4eeb-b32c-c1f0ab7366d0",
"publicKeyMultibase": "bycumafaybswzlqamqqb5pjdh4m3cpvoepntwb3hza5zysrfzh4squhig35xp52lkykfyke"
}
]
}
{
"@context": "https://w3id.org/did/v1",
"id": "did:corda:tcn:dc059bb7-5e58-4f7e-aecb-14df4097cdc1",
"publicKey": [
{
"id": "did:corda:tcn:dc059bb7-5e58-4f7e-aecb-14df4097cdc1#keys-1",
"type": "Ed25519VerificationKey2018",
"controller": "did:corda:tcn:dc059bb7-5e58-4f7e-aecb-14df4097cdc1",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----MCowBQYDK2VwAyEAYZMwlUIQoaYODRIpKXLTGB30w3gfEW78JU8h93VOXdg=-----END PUBLIC KEY-----"
}
]
}
{
"@context": "https://w3id.org/did/v1",
"id": "did:corda:tcn:995591bd-b739-4c39-aeea-c869e2c19af9",
"publicKey": [
{
"id": "did:corda:tcn:995591bd-b739-4c39-aeea-c869e2c19af9#keys-1",
"type": "Ed25519VerificationKey2018",
"controller": "did:corda:tcn:995591bd-b739-4c39-aeea-c869e2c19af9",
"publicKeyJwk": "{\"kty\":\"oct\",\"k\":\"MCowBQYDK2VwAyEABfHj54MBtp6gsWYes8wcYCrOyhZZNBzlGPju-G9ImgI\"}"
}
]
}
Envelopes are implemented as multipart/form-data
HTTP requests with two parts:
Key | Value |
---|---|
instruction |
Instruction JSON |
document |
DID JSON |
This format is chosen to circumvent issues with canonical document representation for hashing.
This is used to create a new DID. Proof of ownership of the document has to be presented in the envelope.
Payload includes:
- The document consisting of the encoded public key, type of public key, controller of public key.
- The instruction consisting of action to perform (create), encoded signature on the document and type of signature.
Instruction:
{
"action": "create",
"signatures": [
{
"id": "did:corda:tcn:a609bcc0-a3a8-11e9-b949-fb002eb572a5#keys-1",
"type": "Ed25519Signature2018",
"signatureBase58": "2M12aBn5ijmmUyHtTf56NTJsUEUbpbqbAgpsvxsfMa2KrL5MR5rGb4dP37QoyRWp94kqreDMV9P4K3QHfE67ypTD"
}
]
}
Document:
{
"@context": "https://w3id.org/did/v1",
"id": "did:corda:tcn:a609bcc0-a3a8-11e9-b949-fb002eb572a5",
"publicKey": [
{
"id": "did:corda:tcn:a609bcc0-a3a8-11e9-b949-fb002eb572a5#keys-1",
"type": "Ed25519VerificationKey2018",
"controller": "did:corda:tcn:a609bcc0-a3a8-11e9-b949-fb002eb572a5",
"publicKeyBase58": "GfHq2tTVk9z4eXgyNRg7ikjUaaP1fuE4Ted3d6eBaYSTxq9iokAwcd16hu8v"
}
]
}
HTTP Request:
curl -X PUT \
http://example.org/did:corda:tcn:a609bcc0-a3a8-11e9-b949-fb002eb572a5 \
-H 'content-type: multipart/form-data' \
-F instruction='{
"action": "create",
"signatures": [
{
"id": "did:corda:tcn:a609bcc0-a3a8-11e9-b949-fb002eb572a5#keys-1",
"type": "Ed25519Signature2018",
"signatureBase58": "2M12aBn5ijmmUyHtTf56NTJsUEUbpbqbAgpsvxsfMa2KrL5MR5rGb4dP37QoyRWp94kqreDMV9P4K3QHfE67ypTD"
}
]
}' \
-F document'={
"@context": "https://w3id.org/did/v1",
"id": "did:corda:tcn:a609bcc0-a3a8-11e9-b949-fb002eb572a5",
"created":"2019-07-11T10:27:27.326Z",
"publicKey": [
{
"id": "did:corda:tcn:a609bcc0-a3a8-11e9-b949-fb002eb572a5#keys-1",
"type": "Ed25519VerificationKey2018",
"controller": "did:corda:tcn:a609bcc0-a3a8-11e9-b949-fb002eb572a5",
"publicKeyBase58": "GfHq2tTVk9z4eXgyNRg7ikjUaaP1fuE4Ted3d6eBaYSTxq9iokAwcd16hu8v"
}
]
}'
Response:
- The API will respond with status
200
for a request with a well-formed instruction and a well-formed document and valid signature(s) and an unused ID. - The API will respond with status
400
for a request with a deformed instruction or a deformed document or at least one invalid signature. - The API will respond with status
409
for a request with an ID that is already taken.
A simple GET
request specifying the id as fragment is used to retrieve a DID document.The DID document contains a list of public keys, the type of the public key,information about encodings used on those public keys and the controller of each public key.
HTTP Request:
curl -X GET http://example.org/did:corda:tcn:a609bcc0-a3a8-11e9-b949-fb002eb572a5
Response:
{
"@context":"https://w3id.org/did/v1",
"id":"did:corda:tcn:a609bcc0-a3a8-11e9-b949-fb002eb572a5",
"created":"2019-07-11T10:27:27.326Z",
"publicKey":[
{
"id":"did:corda:tcn:a609bcc0-a3a8-11e9-b949-fb002eb572a5#keys-1",
"type":"Ed25519VerificationKey2018",
"controller":"did:corda:tcn:a609bcc0-a3a8-11e9-b949-fb002eb572a5",
"publicKeyBase58":"GfHq2tTVk9z4eXgyNRg7ikjUaaP1fuE4Ted3d6eBaYSTxq9iokAwcd16hu8v"
}
]
}
- The API will respond with status
200
for a request with a known ID. - The API will respond with status
404
for a request with an unknown ID. - The API will respond with status
400
for a request with an ID with incorrect format.
Updates use the optional created and updated concepts to mitigate replay attacks.
This means an update will only be successful if the updated
field in the DID document is set to an instant that is later than the instant previously saved with that field.
Should no previous update be recorded, the update will only be successful if the created
field in the document is set to an instant that is later than the instant provided with the update.
The calculation of the current time is done by the DID owner without verification of its accuracy by the consortium. This is appropriate since this field is only used to determine a before/after relationship. Consumers of the DID document need to take into account that this value is potentially inaccurate.
Payload includes:
- The document consisting of the new encoded public key, type of public key, controller of public key.
- The instruction consisting of action to perform (update), encoded signature(s) on this document using all private keys(including the one being added) associated with the public keys in the document and type of signature.
HTTP request:
curl -X POST \
http://example.org/did:corda:tcn:a609bcc0-a3a8-11e9-b949-fb002eb572a5 \
-H 'content-type: multipart/form-data' \
-F instruction='{
"action": "update",
"signatures": [
{
"id":"did:corda:tcn:a609bcc0-a3a8-11e9-b949-fb002eb572a5#keys-1",
"type":"Ed25519Signature2018",
"signatureBase58":"57HQXkem7pXpfHnP3DPTyLqSQB9NuZNj7V4hS61kbkQA28hCuYtSmFQCABj8HBX2AmDss13iDkNY2H3zqRZsYnD4"
},
{
"id":"did:corda:tcn:a609bcc0-a3a8-11e9-b949-fb002eb572a5#keys-2",
"type":"Ed25519Signature2018",
"signatureBase58":"26kkhZbQLSNvEKbPvx18GRfSoVMu2bDXutvnWcQQyrGxqz5VKijkFV2GohbkbafPa2WqVad7wnyLwx1zxjvVfvSa"
}
]
}' \
-F document'={
"@context": "https://w3id.org/did/v1",
"id": "did:corda:tcn:a609bcc0-a3a8-11e9-b949-fb002eb572a5",
"created":"2019-07-11T10:27:27.326Z",
"updated":"2019-07-11T10:29:15.116Z",
"publicKey": [
{
"id": "did:corda:tcn:a609bcc0-a3a8-11e9-b949-fb002eb572a5#keys-2",
"type": "Ed25519VerificationKey2018",
"controller": "did:corda:tcn:a609bcc0-a3a8-11e9-b949-fb002eb572a5",
"publicKeyBase58": "GfHq2tTVk9z4eXgyHhSTmTRf4NFuTv7afqFroA8QQFXKm9fJcBtMRctowK33"
}
]
}'
- The API will respond with status
200
if update is successful. - The API will respond with status
404
for a request with an unknown ID. - The API will respond with status
400
for other cases of incorrect payload (mismatched signatures,malformed document,instruction etc.).
This method is used to disable the identity on the ledger. Once deleted the identity cannot be used again. Delete accepts only instruction as payload , the instruction contains signature(s) for the public key(s) of the latest DID document on the ledger.
Payload includes:
- The instruction consisting of action to perform (delete), encoded signature on the latest DID document on the ledger using all private keys associated with public keys present in the document and type of the signature.
To validate a delete request, the user must provide signature(s) in the instruction, the signature(s) are on the latest did document present in the ledger signed with corresponding private keys for all the public keys present in the document.
HTTP request:
curl -X DELETE \
http://example.org/did:corda:tcn:a609bcc0-a3a8-11e9-b949-fb002eb572a5 \
-H 'content-type: multipart/form-data' \
-F instruction='{
"action": "delete",
"signatures": [
{
"id":"did:corda:tcn:a609bcc0-a3a8-11e9-b949-fb002eb572a5#keys-2",
"type":"Ed25519Signature2018",
"signatureBase58":"26kkhZbQLSNvEKbPvx18GRfSoVMu2bDXutvnWcQQyrGxqz5VKijkFV2GohbkbafPa2WqVad7wnyLwx1zxjvVfvSa"
}
]
}'
- The API will respond with status
200
if delete is successful. - The API will respond with status
404
for a request with an unknown ID. - The API will respond with status
400
for other cases of incorrect payload (mismatched signatures, malformed instruction etc.).
Corda DID Flows (did-witness-flows
)
The DID Flows is the CorDapp component to be deployed by consortium member nodes. It provides Method specific flows to create, read, update or delete DID documents. The DID flows can be invoked from RPC client or from another flows.
This is used to create a new DID. Proof of ownership of the document has to be presented in the envelope as outlined in the API format.
-
invoke CreateDidFlow via RPC:
rpc.startFlowDynamic(CreateDidFlow::class.java, envelope)
-
invoke CreateDidFlow from another flow:
subFlow(CreateDidFlow(envelope))
where envelope is an instance of type DidEnvelope
This is used to fetch a did
document from node's local vault. It returns an instance of type DidDocument
-
invoke FetchDidDocumentFlow via RPC:
rpc.startFlowDynamic(FetchDidDocumentFlow::class.java, linearId)
-
invoke FetchDidDocumentFlow from another flow:
subFlow(FetchDidDocumentFlow(linearId))
where linearId is an instance of type UniqueIdentifier
and it is the UUID part of the did
.
There might be a case where a node which is not part of the DID Business Network may request DID document from one of the DID consortium nodes.
In such situations, nodes can invoke FetchDidDocumentFromRegistryNodeFlow
defined in (did-flows
) module.
-
invoke FetchDidFetchDidDocumentFromRegistryNodeFlowDocumentFlow via RPC:
rpc.startFlowDynamic(FetchDidDocumentFromRegistryNodeFlow::class.java, didRegistryNode, linearId)
-
invoke FetchDidDocumentFromRegistryNodeFlow from another flow:
subFlow(FetchDidDocumentFromRegistryNodeFlow(didRegistryNode, linearId))
where linearId is an instance of type UniqueIdentifier
and it is the UUID part of the did
&& didRegistryNode is an instance of type Party
representing the did
consortium node.
This is used to update an existing DID.
-
invoke UpdateDidFlow via RPC:
rpc.startFlowDynamic(UpdateDidFlow::class.java, envelope)
-
invoke UpdateDidFlow from another flow:
subFlow(UpdateDidFlow(envelope))
where envelope is an instance of type DidEnvelope
This is used to disable an existing DID. Delete operation introduces no changes to the DidDocument. It expires the DidState and is marked as Consumed
on the ledger.
To validate a delete request, the user must provide signature(s) in the instruction, the signature(s) are on the latest did
document present in the ledger signed with corresponding private keys for all the public keys present in the document.
-
invoke DeleteDidFlow via RPC:
rpc.startFlowDynamic(DeleteDidFlow::class.java, instruction, did)
-
invoke DeleteDidFlow from another flow:
subFlow(DeleteDidFlow(instruction, did))
where instruction is the instruction JSON object (in string form) containing signatures of did-owner on the did-document to be deactivated
&& did
is the did
to be deleted.
Detailed design of the CorDapp can be viewed here.
The steps for setting up the project are here.
Digital signatures and public key encoding schemes supported are shown in the table below.
publicKeyPem |
publicKeyJwk |
publicKeyHex |
publicKeyBase64 |
publicKeyBase58 |
publicKeyMultibase |
|
---|---|---|---|---|---|---|
Ed25519Signature2018 |
✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
RsaSignature2018 |
✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
EcDsaSASignatureSecp256k1 |
✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
The following multibase encoding schemes are not supported as defined in the Multibase data format
- BASE1
- Z-Base32
- BASE32_PAD
- BASE32_HEX_PAD
- BASE64_URL_PAD
Contribution guidelines can be found here.
To raise an issue, please follow the format here
To raise a feature request, follow the format here
The Corda DID method is the result of a collaboration between R3 and Persistent Systems.