.. _encoding-overview:
Encoding Overview
=================
.. _object-format:
Object Format
-------------
The :term:`Relay` and :term:`Client` use the `CBOR `_ encoding scheme for objects and to communicate changes made to a :term:`Shop`. We are using this format for a number of reasons: First, it is a scheme standardized by the IETF (as `RFC 8949 `_) and thus comes with a wide range of implementations in many languages. Secondly, it comes with canonical encoding mode, which our initial protobuf implementation lacked. This last point allows us to encode all objects of a :term:`Shop` such that we can create a state, or merkle root. This hash allows :term:`Users` to check if the data they have received is complete and consistent.
The object types are as follows:
.. TODO: link to generated code from either CDDL or the Go struct definitions
- :cbor:ref:`Manifest`: the shop's metadata
- :cbor:ref:`Listings`: the items a shop lists for sale
- :cbor:ref:`Tags`: the tags a shop uses to categorize its listings
- :cbor:ref:`Inventory`: how many items are currently available for sale
- :cbor:ref:`Orders`: the list of orders, what items were ordered and by whom
- :cbor:ref:`Accounts`: the list of user accounts
.. _object-visibility:
Object Visibility
-----------------
Not all objects are visible to every :term:`User` of a :term:`Shop`. Certain information is private to :term:`Clerks`.
The following objects are publicly visible:
- The :cbor:ref:`Manifest`, :cbor:ref:`Listings` and :cbor:ref:`Tags`
- :cbor:ref:`Accounts` for :term:`Clerks`, since :term:`Guests` need to verify the event signatures,
Orders and the patches to them are restricted. All of them are visible to :term:`Clerks` and **Guest** users can only see those that they created.
Changes to the inventory are restricted to :term:`Clerks` as well.
.. _object-patches:
Patching Objects
----------------
To not re-transmit objects all the time, we drew inspiration from `JSON Patch `_ but applied it to using the CBOR scheme. In a nutshell, changes to an object are operationalized, and which value is changed is indicated by the path to the property.
Here is an example, changing the price of a :cbor:ref:`Listing` to 100:
.. code-block:: json
{ "op": "replace", "path": ["Listings", 1, "Price"], "value": 100 }
To build transactional blocks of patches, we are introducing the concept of a PatchSet. This is a list of patches that are applied in order to build up the next state of a :term:`Shop`. Either they all apply correctly or the :term:`Relay` refuses the write request.
More Patch Examples
~~~~~~~~~~~~~~~~~~~
Here are some examples of CBOR patches that can be applied to shop objects:
Adding Order Items and Increasing Quantity
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In this example we are patching the following Order:
.. code-block:: json
{
"ID": 42
"State": 1
"Items": []
}
with the following PatchSet with four operations:
* Appending a new :cbor:ref:`OrderedItem` to an :cbor:ref:`Order`
* Appending another :cbor:ref:`OrderedItem` to the same :cbor:ref:`Order`
* Incrementing the quantity of the first :cbor:ref:`OrderedItem`
* and finally, updating the :cbor:ref:`OrderState` from 1 (`Open`) to 3 (`Committed`)
.. code-block:: json
[
{ "op": "append", "path": ["Orders", 42, "Items"], "value": { "ListingID": 101, "Quantity": 1 } },
{ "op": "append", "path": ["Orders", 42, "Items"], "value": { "ListingID": 202, "VariationIDs": ["large", "blue"], "Quantity": 2 } },
{ "op": "increment", "path": ["Orders", 42, "Items", 0, "Quantity"], "value": 3 }
{ "op": "replace", "path": ["Orders", 42, "State"], "value": 3 }
]
The resulting Order is this:
.. code-block:: json
{
"ID": 42
"State": 3
"Items": [
{ "ListingID": 101, "Quantity": 4 },
{ "ListingID": 202, "Quantity": 2, "VariationIDs": ["large", "blue"] }
]
}
Updating Shop Manifest
^^^^^^^^^^^^^^^^^^^^^^
This example shows a PatchSet with two operations that modify the :term:`Shop`'s :cbor:ref:`Manifest`:
* Adding a new (escrow) :cbor:ref:`Payee` to the :cbor:ref:`Manifest`
* Adding a new :cbor:ref:`ShippingRegion` with a :cbor:ref:`PriceModifier`
.. code-block:: js
[
{
"op": "add",
"path": ["Manifest", "Payees", 1, Uint8Array("0x1234567890abcdef1234567890abcdef12345678")],
"value": { "CallAsContract": true }
},
{
"op": "add",
"path": ["Manifest", "ShippingRegions", "Germany"],
"value": {
"Country": "Germany",
/* empty fields work as wildcards */
"PostalCode": "",
"City": "",
"PriceModifiers": {
"standard": {
"ModificationAbsolute": {
"Amount": 100,
"Plus": false
}
}
}
}
}
]
Updating Listing Images
^^^^^^^^^^^^^^^^^^^^^^^
This example shows a PatchSet that updates the images of a :cbor:ref:`Listing`:
* Removing the first two images from the :cbor:ref:`ListingMetadata`
* Adding two new images to the end of the array
.. code-block:: json
[
{ "op": "remove", "path": ["Listings", 123, "Metadata", "Images", 0] },
{ "op": "remove", "path": ["Listings", 123, "Metadata", "Images", 0] },
{ "op": "append", "path": ["Listings", 123, "Metadata", "Images"], "value": "https://example.com/images/product123-front.jpg" },
{ "op": "append", "path": ["Listings", 123, "Metadata", "Images"], "value": "https://example.com/images/product123-back.jpg" }
]
.. _object-signatures:
Object Signatures
-----------------
We use `EIP191 `_ to sign the CBOR encoded header of a PatchSet.
The header contains the following information:
- The shop ID
- The KeyCard nonce (usually a counter)
- The MMR root hash of all objects in the PatchSet
- The timestamp of the PatchSet
A signed PatchSet thus consists of:
- The above header
- A signature of the header
- A list of patches
.. _patchset-mmr:
Merkle Mountain Ranges (MMRs)
-----------------------------
While our PatchSets don't grow, we still used the MMR scheme defined in `this IETF ID `_. We picked it because it clearly defines how to create roots and accumulators, whereas we found a lot of ambiguity and diversions in other Merkle tree implementations.
In general, Merkle trees allow us to make inclusion proofs, which are a way to prove that a certain object is part of a Merkle tree by providing the necessary sibling nodes to reconstruct the path from a leaf to the root. This allows us to stream only parts of PatchSets to a client, which can then verify the integrity of the received data. Say, if they only require all the patches for a certain listing, they can download only those. See :ref:`ref_market.mass.SubscriptionPushRequest` for more.
.. _cbor-references:
CBOR References
---------------
.. cbor:file:: shop_logical.cddl
:title: Shop datastructures