- Authors: Daniel Hardman
- Status: PROPOSED
- Since: 2019-07-03
- Status Note: used by the peer DID method spec. Implementation beginning.
- Supersedes: Indy HIPE PR #104
- Start Date: 2018-10-01
- Tags: feature, decorator, protocol
Define a set of non-centralized protocols (that is, ones that do not involve a common store of state like a blockchain), whereby parties using peer DIDs can synchronize the state of their shared relationship by direct communication with one another.
- 20240320: Clarification removing references to retired
~please_ack
decorator and RFC.
For Alice and Bob to interact, they must establish and maintain state. This state includes all the information in a DID Document: endpoint, keys, and associated authorizations.
The DID exchange protocol describes how these DID Docs are initially exchanged as a relationship is built. However, its mandate ends when a connection is established. This RFC focuses on how peers maintain their relationship thereafter, as DID docs evolve.
Note 1: This RFC assumes you are thoroughly familiar with terminology and constructs from the peer DID method spec. Check there if you need background.
Note 2: Most protocols between identity owners deal only with messages that cross a domain boundary--what Alice sends to Bob, or vice versa. What Alice does internally is generally none of Bob's business, since interoperability is a function of messages that are passed to external parties, not events that happen inside one's own domain. However, this protocol has some special requirements. Alice may have multiple agents, and Bob's behavior must account for the possibility that each of them has a different view of current relationship state. Alice has a responsibility to share and harmonize the view of state among her agents. Bob doesn't need to know exactly how she does it--but he does need to know that she's doing it, somehow--and he may need to cooperate with Alice to intelligently resolve divergences. For this reason, we describe the protocol as if it involved message passing within a domain in addition to message passing across domains. This is a simplification. The true, precise requirement for compliance is that implementers must pass messages across domains as described here, and they must appear to an outside observer as if they were passing messages within their domain as the protocol stipulates--but if they achieve the intra-domain results using some other mechanism besides DIDComm message passing, that is fine.
This RFC defines the sync_connection
protocol, version 1.x, as identified by the
following PIURI:
https://didcomm.org/sync_connection/1.0
Of course, subsequent evolutions of the protocol will replace 1.0
with
an appropriate update per semver
rules.
A related, minor protocol is also defined in subdocs of this RFC:
The only role defined in this protocol is peer
. However, see this note in the
peer DID method spec
for some subtleties.
This is a steady-state protocol, meaning that the state of participants does
not change. Instead, all participants are continuously in a syncing
state.
This message announces that the sender wants to synchronize state with the recipient. This could happen because the sender suspects they are out of sync, or because the sender wants to change the state by announcing new, never-before-seen information. The recipient can be another agent within the same sovereign domain, or it can be an agent on the other side of the relationship. A sample looks like this:
{
"@type": "https://didcomm.org/sync-connection/1.0/sync_state",
"@id": "e61586dd-f50e-4ed5-a389-716a49817207",
"for": "did:peer:11-479cbc07c3f991725836a3aa2a581ca2029198aa420b9d99bc0e131d9f3e2cbe",
"base_hash": "d48f058771956a305e12a3b062a3ac81bd8653d7b1a88dd07db8f663f37bf8e0",
"base_hash_time": "2019-07-23 18:05:06.123Z",
"deltas": [
{
"id": "040aaa5e-1a27-40d8-8d53-13a00b82d235",
"change": "ewogICJwdWJsaWNLZXkiOiBbCiAgICB...ozd1htcVBWcGZrY0pDd0R3biIKICAgIH0KICBdCn0=",
"by": [ {"key": "H3C2AVvL", "sig": "if8ooA+32YZc4SQBvIDDY9tgTa...i4VvND87PUqq5/0vsNFEGIIEDA=="} ],
"when": "2019-07-18T15:49:22.03Z"
}
]
}
Note that the values in the change
and sig
fields have been shortened
for readability.
The properties in this message include:
*for
: Identifies which state is being synchronized.
base_hash
: Identifies a shared state against which deltas should be applied. See State Hashes for more details.base_hash_time
: An ISO 8601-formatted UTC timestamp, identifying when the sender believes that the base hash became the current state. This value need not be highly accurate, and different agents in Alice and Bob's ecosystem may have different opinions about an appropriate timestamp for the selected base hash. Like timestamps in email headers, it merely provides a rough approximation of timeframe.deltas
: Gives a list of deltas that should be applied to the DID doc, beginning at the specified state.
When this message is received, the following processing happens:
- The
base_hash
anddeltas
properties are checked for consistency. If any errors are detected, aproblem_report
message is returned, using message threading to pinpoint the message that triggered the problem. No further processing occurs. - If the recipient already has the same state, it sends an ACK.
- If the recipient knew about a subset of the delta, but not all of it, it applies what is left of the delta, and sends an ACK.
- If the recipient has a more evolved state, the recipient sends a reply
that is a new
sync_state
message informing the sender of new information. As with the ACKs, this new message is known to be a reply to the originalsync_state
because its~thread
decorator identifies the previous message's@id
as itsthid
. - If the recipient does not recognize the
base_hash
, it selects a hash from a point in time earlier thanbase_hash_time
and sends back a newsync_state
message with that earlier base. - Because of the nature of the CRDT rules in the peer DID spec, truly
problematic merge conflicts cannot occur. All objects in the DID doc that
have an
id
property are immutable, and ordering of operations does not need to be highly consistent. Furthermore, an object in both deleted and undeleted state must be deemed to be deleted. However, if the recipient detects a trivial conflict, the delta with theid
that sorts smaller/lower is selected.
To reliably describe the state of a DID doc at any given moment, we need a quick way to characterize its content. We could do this with a merkle tree, but the strong ordering of that structure is problematic--different participants may receive different deltas in different orders, and this is okay. What matters is whether they have applied the same set of deltas.
To achieve this goal, the id
properties of all received deltas are sorted
and concatenated, and then the string undergoes a SHA256 hash. This produces
a state hash.
The following best practices will dramatically improve the robustness of state synchronization, both within and across domains. Software implementing this protocol is not required to do any of these things, but they are strongly recommended.
Agents using peer DIDs should
attach the ~state
decorator to messages to help
each other discover when state synchronization is needed. This decorator has the
following format:
"~state": [
{"did": "<my did>", "state_hash": "<my state hash>"},
{"did": "<your did>", "state_hash": "<your state hash>"}
]
In n-wise relationships, there may be more than 2 entries in the list.
The goal is to always describe the current known state hashes for each
domain. It is also best practice for the recipient of the message to send a
sync_state
message back to the sender any time it detects a discrepancy.
Agents should never commit to a change of state until they know that at least one other agent (on either side of the relationship) agrees to the change. This will significantly decrease the likelihood of merge conflicts. For example, an agent that wants to rotate a key should report the key rotation to someone, and receive an ACK, before it commits to use the new key. This guarantees that there will be gravitas and confirmation of the change, and is a reasonable requirement, since a change that nobody knows about is useless, anyway.
It is best practice for routing agents (typically in the cloud) to enforce the following rules:
- Never deliver a message to an edge agent that shouldn't receive it, according to state
that the routing agent knows. Instead, reply with a
problem_report
about the state being out of sync, followed by async_state
message to initiate a reconciliation of the differences. - Never deliver a message from an agent that shouldn't be sending it, according to state that the routing agent knows. As with the previous rule, initiate a reconciliation first.
- Attempt to propagate state changes proactively.
Any time that an agent has reason to suspect that it may be out of sync, it should attempt to reconcile. For example, if a mobile device has been turned off for an extended period of time, it should check with other agents to see if state has evolved, once it is able to communicate again.
Because this protocol encapsulates a lot of potential complexity, and many corner cases, it is particularly important that implementations exercise the full range of scenarios in the Test Cases doc. Community members are encouraged to submit new test cases if they find situations that are not covered.
[TODO: create state machine matrices that show which messages can be sent in which states, causing which transitions]
[TODO: explain every possible field of every possible message type]
[TODO: define some localized strings that could be used with these messages, in errors or at other generally useful points?]
The following lists the implementations (if any) of this RFC. Please do a pull request to add your implementation. If the implementation is open source, include a link to the repo or to the implementation within the repo. Please be consistent in the "Name" field so that a mechanical processing of the RFCs can generate a list of all RFCs supported by an Aries implementation.
Name / Link | Implementation Notes |
---|---|