Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove JSON string escaping in diff messages #598

Merged
merged 5 commits into from
Jan 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/releases/v0.5.0-dev.3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This release introduces a breaking change for diff updates created by versions `v0.5.0-dev.1` and `v0.5.0-dev.2` (previous diff updates from `<=v0.4.0` are already incompatible due to breaking changes to the document and message structure in `v0.5.0-dev.1`). To migrate, please publish an integration update containing all diff changes to prevent unexpected changes to resolved DID Documents.
1 change: 1 addition & 0 deletions .github/releases/wasm-v0.5.0-dev.3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This release introduces a breaking change for diff updates created by versions `v0.5.0-dev.1` and `v0.5.0-dev.2` (previous diff updates from `<=v0.4.0` are already incompatible due to breaking changes to the document and message structure in `v0.5.0-dev.1`). To migrate, please publish an integration update containing all diff changes to prevent unexpected changes to resolved DID Documents.
8 changes: 4 additions & 4 deletions bindings/wasm/docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -775,7 +775,7 @@ Returns the DID of the associated DID Document.
<a name="DiffMessage+diff"></a>

### diffMessage.diff ⇒ <code>string</code>
Returns the raw contents of the DID Document diff.
Returns the raw contents of the DID Document diff as a JSON string.

NOTE: clones the data.

Expand Down Expand Up @@ -869,7 +869,7 @@ with the given Document.
* [.signPresentation(data, args, options)](#Document+signPresentation) ⇒ [<code>Presentation</code>](#Presentation)
* [.signData(data, args, options)](#Document+signData) ⇒ <code>any</code>
* [.verifyData(data, options)](#Document+verifyData) ⇒ <code>boolean</code>
* [.diff(other, message, key, method)](#Document+diff) ⇒ [<code>DiffMessage</code>](#DiffMessage)
* [.diff(other, message_id, key, method)](#Document+diff) ⇒ [<code>DiffMessage</code>](#DiffMessage)
* [.verifyDiff(diff)](#Document+verifyDiff)
* [.merge_diff(diff)](#Document+merge_diff)
* [.integrationIndex()](#Document+integrationIndex) ⇒ <code>string</code>
Expand Down Expand Up @@ -1127,7 +1127,7 @@ Verifies the authenticity of `data` using the target verification method.

<a name="Document+diff"></a>

### document.diff(other, message, key, method) ⇒ [<code>DiffMessage</code>](#DiffMessage)
### document.diff(other, message_id, key, method) ⇒ [<code>DiffMessage</code>](#DiffMessage)
Generate a `DiffMessage` between two DID Documents and sign it using the specified
`key` and `method`.

Expand All @@ -1136,7 +1136,7 @@ Generate a `DiffMessage` between two DID Documents and sign it using the specifi
| Param | Type |
| --- | --- |
| other | [<code>Document</code>](#Document) |
| message | <code>string</code> |
| message_id | <code>string</code> |
| key | [<code>KeyPair</code>](#KeyPair) |
| method | <code>string</code> |

Expand Down
9 changes: 5 additions & 4 deletions bindings/wasm/src/did/wasm_diff_message.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Copyright 2020-2021 IOTA Stiftung
// Copyright 2020-2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use std::ops::Deref;
use std::str::FromStr;

use identity::core::ToJson;
use identity::iota::DiffMessage;
use identity::iota::MessageId;
use identity::iota::TangleRef;
Expand Down Expand Up @@ -35,12 +36,12 @@ impl WasmDiffMessage {
self.id()
}

/// Returns the raw contents of the DID Document diff.
/// Returns the raw contents of the DID Document diff as a JSON string.
///
/// NOTE: clones the data.
#[wasm_bindgen(getter = diff)]
pub fn diff(&self) -> String {
self.0.diff().to_owned()
pub fn diff(&self) -> Result<String> {
self.0.diff().to_json().wasm_result()
}

/// Returns the message_id of the DID Document diff.
Expand Down
6 changes: 3 additions & 3 deletions bindings/wasm/src/did/wasm_document.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2020-2021 IOTA Stiftung
// Copyright 2020-2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use std::str::FromStr;
Expand Down Expand Up @@ -315,12 +315,12 @@ impl WasmDocument {
/// Generate a `DiffMessage` between two DID Documents and sign it using the specified
/// `key` and `method`.
#[wasm_bindgen]
pub fn diff(&self, other: &WasmDocument, message: &str, key: &KeyPair, method: &str) -> Result<WasmDiffMessage> {
pub fn diff(&self, other: &WasmDocument, message_id: &str, key: &KeyPair, method: &str) -> Result<WasmDiffMessage> {
self
.0
.diff(
&other.0,
MessageId::from_str(message).wasm_result()?,
MessageId::from_str(message_id).wasm_result()?,
key.0.private(),
method,
)
Expand Down
60 changes: 44 additions & 16 deletions documentation/docs/specs/did/iota_did_method_spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ keywords:

# IOTA DID Method Specification

Version 0.4-draft by Jelle Millenaar, IOTA Foundation
Version 0.5-draft by Jelle Millenaar, IOTA Foundation

## Abstract

Expand Down Expand Up @@ -121,23 +121,23 @@ Example of an Integration DID Message:
```json
{
"doc": {
"id": "did:iota:2SDJS9q5ktq73ir8LLtLtF1pWLsmrcSnU67icNzin1oZ",
"id": "did:iota:DqmknfA6pF6LpaGeHs3qcLjCPicWNgaG9AvinvpTKCGD",
"capabilityInvocation": [
{
"id": "did:iota:2SDJS9q5ktq73ir8LLtLtF1pWLsmrcSnU67icNzin1oZ#sign-0",
"controller": "did:iota:2SDJS9q5ktq73ir8LLtLtF1pWLsmrcSnU67icNzin1oZ",
"id": "did:iota:DqmknfA6pF6LpaGeHs3qcLjCPicWNgaG9AvinvpTKCGD#sign-0",
"controller": "did:iota:DqmknfA6pF6LpaGeHs3qcLjCPicWNgaG9AvinvpTKCGD",
"type": "Ed25519VerificationKey2018",
"publicKeyMultibase": "zAibEeQAru66yK8TBGvzU2VXGQzQNo3n3DZmnRgTi4Y5c"
"publicKeyMultibase": "z9wLQANgLeBLaKzejMuxa61gswLx6eYCsgK1tP3aW5SQA"
}
]
},
"meta": {
"created": "2021-12-13T16:20:25Z",
"updated": "2021-12-13T16:20:25Z",
"created": "2022-01-17T19:13:17Z",
"updated": "2022-01-17T19:13:17Z",
"proof": {
"type": "JcsEd25519Signature2020",
"verificationMethod": "#sign-0",
"signatureValue": "5THVPoHymZMoQi7ZbbQr5ff7VkboiJgcjkDx3X4DAHTEnn3nBFkx6nC5dfwxcT7DuUH63rttAb9e91DQ1rR6ofqP"
"signatureValue": "4JouG4VVZircUnWZGLexW5nnV2EWHvNinySF7YJauhNRdQCC94w33jhFF4v1tgxcRQrxA2Mye78Np52HR5FZ3va5"
}
}
}
Expand All @@ -152,19 +152,33 @@ A Differentiation (Diff) DID message does not contain a valid DID Document. Inst
* A Diff DID message MUST have at least the following attributes:
* `id` (REQUIRED): This field helps link the update to a DID. The value of `id` MUST be a string that references the DID that this update applies to.
* `previousMessageId` (REQUIRED): This field provides an immutable link to the previous DID document that is updated and is used for basic ordering of the DID messages, creating a chain. The value of `previousMessageId` MUST be a string that contains an IOTA MessageId from the previous DID message it updates, which references either a Diff or Int Chain message. Read the [Previous Message Id](#previous-message-id) section for more information.
* `diff` (REQUIRED): A Differentiation object containing all the changes compared to the DID Document it references in the `previousMessageId` field. The value of `diff` MUST be an escaped JSON string following the [Anatomy of the Diff object](#anatomy-of-the-diff-object) definition.
* `diff` (REQUIRED): A Differentiation object containing all the changes compared to the DID Document it references in the `previousMessageId` field. The value of `diff` MUST be a JSON object following the [Anatomy of the Diff object](#anatomy-of-the-diff-object) definition.
* `proof` (REQUIRED): This field provides a cryptographic proof on the message that proves ownership over the DID Document. The value of the `proof` object MUST contain an object as defined by [Anatomy of the Proof object](#anatomy-of-the-proof-object).

Example of a Diff DID message:
```json
{
"id": "did:iota:2SDJS9q5ktq73ir8LLtLtF1pWLsmrcSnU67icNzin1oZ",
"diff": "{\"doc\":{\"service\":[{\"id\":\"did:iota:2SDJS9q5ktq73ir8LLtLtF1pWLsmrcSnU67icNzin1oZ#service-1\",\"type_\":\"LinkedDomains\",\"service_endpoint\":\"\\\"https://example.com/\\\"\",\"properties\":null}]},\"meta\":{\"updated\":\"2021-12-13T16:20:43Z\"}}",
"previousMessageId": "844dd476cf6f0e7d4db1aae8b18e84d0dba951ac7beaacbf4f5748631929f09e",
"id": "did:iota:DqmknfA6pF6LpaGeHs3qcLjCPicWNgaG9AvinvpTKCGD",
"diff": {
"doc": {
"service": [
{
"id": "did:iota:DqmknfA6pF6LpaGeHs3qcLjCPicWNgaG9AvinvpTKCGD#linked-domain-1",
"type_": "LinkedDomains",
"service_endpoint": "https://example.com/",
"properties": null
}
]
},
"meta": {
"updated": "2022-01-17T19:13:35Z"
}
},
"previousMessageId": "c8a6213cf87e3917ef20936ab215ba1825b02e2f235f258d1f4eaddb799bac65",
"proof": {
"type": "JcsEd25519Signature2020",
"verificationMethod": "#sign-0",
"signatureValue": "21wWP4prYn2YLvQabCsWT3qc1Q84Am8q6SRLuQfkXqB6Pm3be47kTQD3axdvidntavKk3PoJtV35XusTFyfJCy3m"
"signatureValue": "3jGSsgWbaDv1mtkbA8sqBA62nm9omUtTVHonTGdafv3ftbQqvYLffe6AdoRKPQHrXUnZnXTB6TDtD66a8zXMQdRp"
}
}
```
Expand Down Expand Up @@ -201,13 +215,27 @@ Example `proof` using the `JcsEd25519Signature2020` method:

### Anatomy of the Diff object

The `diff` object MUST contain all the differences between the current and previous DID Document and its DID Document Metadata. The differentiation is formatted as an escaped JSON object, that includes the differences between the two DID Document objects and the two DID Document Metadata objects. Exact details of how this is generated will be added later.
The `diff` object MUST contain all the differences between the current and previous DID Document and its DID Document Metadata. The differentiation is formatted as a JSON object that includes the differences between the two DID Document objects and the two DID Document Metadata objects. Exact details of how this is generated will be added later.

Example `diff` of adding a new service entry to the document and changing the `updated` field in the metadata:
```json
{
"diff": "{\"doc\":{\"service\":[{\"id\":\"did:iota:2SDJS9q5ktq73ir8LLtLtF1pWLsmrcSnU67icNzin1oZ#service-1\",\"type_\":\"LinkedDomains\",\"service_endpoint\":\"\\\"https://example.com/\\\"\",\"properties\":null}]},\"meta\":{\"updated\":\"2021-12-13T16:20:43Z\"}}"
}
"diff": {
"doc": {
"service": [
{
"id": "did:iota:DqmknfA6pF6LpaGeHs3qcLjCPicWNgaG9AvinvpTKCGD#linked-domain-1",
"type_": "LinkedDomains",
"service_endpoint": "https://example.com/",
"properties": null
}
]
},
"meta": {
"updated": "2022-01-17T19:13:35Z"
}
}
}
```

## CRUD Operations
Expand Down
59 changes: 24 additions & 35 deletions identity-did/src/diff/diff_service.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
// Copyright 2020-2021 IOTA Stiftung
// Copyright 2020-2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use serde::Deserialize;
use serde::Serialize;

use identity_core::common::Object;
use identity_core::convert::FromJson;
use identity_core::convert::ToJson;
use identity_core::diff::Diff;
use identity_core::diff::DiffString;
use identity_core::diff::Error;
Expand All @@ -26,7 +24,7 @@ where
#[serde(skip_serializing_if = "Option::is_none")]
type_: Option<DiffString>,
#[serde(skip_serializing_if = "Option::is_none")]
service_endpoint: Option<DiffString>,
service_endpoint: Option<<ServiceEndpoint as Diff>::Type>,
#[serde(skip_serializing_if = "Option::is_none")]
properties: Option<<T as Diff>::Type>,
}
Expand Down Expand Up @@ -128,36 +126,37 @@ where
Ok(DiffService {
id: Some(self.id().to_string().into_diff()?),
type_: Some(self.type_().to_string().into_diff()?),
service_endpoint: Some(self.service_endpoint().to_string().into_diff()?),
properties: Some(self.properties().clone().into_diff()?),
service_endpoint: Some(self.service_endpoint.into_diff()?),
properties: Some(self.properties.into_diff()?),
})
}
}

impl Diff for ServiceEndpoint {
type Type = DiffString;
type Type = ServiceEndpoint;

fn diff(&self, other: &Self) -> identity_core::diff::Result<Self::Type> {
self
.to_json()
.map_err(identity_core::diff::Error::diff)?
.diff(&other.to_string())
if self != other {
Ok(other.clone())
} else {
Ok(self.clone())
}
}

fn merge(&self, diff: Self::Type) -> identity_core::diff::Result<Self> {
self
.to_json()
.map_err(identity_core::diff::Error::diff)?
.merge(diff)
.and_then(|this| Self::from_json(&this).map_err(identity_core::diff::Error::merge))
if self != &diff {
Ok(diff)
} else {
Ok(self.clone())
}
}

fn from_diff(diff: Self::Type) -> identity_core::diff::Result<Self> {
String::from_diff(diff).and_then(|this| Self::from_json(&this).map_err(identity_core::diff::Error::convert))
Ok(diff)
}

fn into_diff(self) -> identity_core::diff::Result<Self::Type> {
self.to_json().map_err(identity_core::diff::Error::diff)?.into_diff()
Ok(self)
}
}

Expand Down Expand Up @@ -220,17 +219,14 @@ mod test {
fn test_service_endpoint_one() {
let service = service();
let mut new = service.clone();
let new_url = "did:test:1234#service".to_string();
*new.service_endpoint_mut() = Url::parse(new_url).unwrap().into();
let new_url = Url::parse("did:test:1234#service").unwrap();
*new.service_endpoint_mut() = ServiceEndpoint::One(new_url.clone());

let diff = service.diff(&new).unwrap();
assert!(diff.id.is_none());
assert!(diff.properties.is_none());
assert!(diff.type_.is_none());
assert_eq!(
diff.service_endpoint,
Some(DiffString(Some("\"did:test:1234#service\"".to_owned())))
);
assert_eq!(diff.service_endpoint, Some(ServiceEndpoint::One(new_url)),);
let merge = service.merge(diff).unwrap();
assert_eq!(merge, new);
}
Expand All @@ -244,17 +240,15 @@ mod test {
Url::parse("https://example.com/").unwrap(),
Url::parse("did:test:1234#service").unwrap(),
];
*new.service_endpoint_mut() = ServiceEndpoint::Set(new_url_set.try_into().unwrap());
*new.service_endpoint_mut() = ServiceEndpoint::Set(new_url_set.clone().try_into().unwrap());

let diff = service.diff(&new).unwrap();
assert!(diff.id.is_none());
assert!(diff.properties.is_none());
assert!(diff.type_.is_none());
assert_eq!(
diff.service_endpoint,
Some(DiffString(Some(
r#"["https://example.com/","did:test:1234#service"]"#.to_owned()
)))
Some(ServiceEndpoint::Set(new_url_set.try_into().unwrap())),
);
let merge = service.merge(diff).unwrap();
assert_eq!(merge, new);
Expand All @@ -275,18 +269,13 @@ mod test {
.try_into()
.unwrap(),
);
*new.service_endpoint_mut() = ServiceEndpoint::Map(new_url_map);
*new.service_endpoint_mut() = ServiceEndpoint::Map(new_url_map.clone());

let diff = service.diff(&new).unwrap();
assert!(diff.id.is_none());
assert!(diff.properties.is_none());
assert!(diff.type_.is_none());
assert_eq!(
diff.service_endpoint,
Some(DiffString(Some(
r#"{"origins":["https://example.com/","did:test:1234#service"]}"#.to_owned()
)))
);
assert_eq!(diff.service_endpoint, Some(ServiceEndpoint::Map(new_url_map)),);
let merge = service.merge(diff).unwrap();
assert_eq!(merge, new);
}
Expand Down
15 changes: 6 additions & 9 deletions identity-iota/src/diff/diff_message.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
// Copyright 2020-2021 IOTA Stiftung
// Copyright 2020-2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use serde;
use serde::Deserialize;
use serde::Serialize;

use identity_core::convert::FromJson;
use identity_core::convert::ToJson;
use identity_core::crypto::SetSignature;
use identity_core::crypto::Signature;
use identity_core::crypto::TrySignature;
Expand All @@ -27,7 +25,7 @@ use crate::tangle::TangleRef;
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct DiffMessage {
pub(crate) id: IotaDID,
pub(crate) diff: String,
pub(crate) diff: DiffIotaDocument,
#[serde(
rename = "previousMessageId",
default = "MessageId::null",
Expand All @@ -46,7 +44,7 @@ impl DiffMessage {
/// The `previous_message_id` is included verbatim in the output, and the `proof` is `None`. To
/// set a proof, use the `set_signature()` method.
pub fn new(current: &IotaDocument, updated: &IotaDocument, previous_message_id: MessageId) -> Result<Self> {
let diff: String = <IotaDocument as Diff>::diff(current, updated)?.to_json()?;
let diff: DiffIotaDocument = <IotaDocument as Diff>::diff(current, updated)?;

Ok(Self {
id: current.id().clone(),
Expand All @@ -63,8 +61,8 @@ impl DiffMessage {
}

/// Returns the raw contents of the DID Document diff.
pub fn diff(&self) -> &str {
&*self.diff
pub fn diff(&self) -> &DiffIotaDocument {
&self.diff
}

/// Returns the Tangle message id of the previous DID Document diff.
Expand All @@ -80,8 +78,7 @@ impl DiffMessage {
/// Returns a new DID Document which is the result of merging `self`
/// with the given Document.
pub fn merge(&self, document: &IotaDocument) -> Result<IotaDocument> {
let diff: DiffIotaDocument = DiffIotaDocument::from_json(&self.diff)?;
let merged: IotaDocument = Diff::merge(document, diff)?;
let merged: IotaDocument = Diff::merge(document, self.diff.clone())?;
Ok(merged)
}
}
Expand Down