Encoding Overview¶
Object Format¶
The Relay and Client use the CBOR encoding scheme for objects and to communicate changes made to a 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 Shop such that we can create a state, or merkle root. This hash allows Users to check if the data they have received is complete and consistent.
The object types are as follows:
Object Visibility¶
Not all objects are visible to every User of a Shop. Certain information is private to Clerks.
The following objects are publicly visible:
Orders and the patches to them are restricted. All of them are visible to Clerks and Guest users can only see those that they created.
Changes to the inventory are restricted to Clerks as well.
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 Listing
to 100:
{ "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 Shop. Either they all apply correctly or the 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:
{
"ID": 42
"State": 1
"Items": []
}
with the following PatchSet with four operations:
Appending a new
OrderedItem
to anOrder
Appending another
OrderedItem
to the sameOrder
Incrementing the quantity of the first
OrderedItem
and finally, updating the
OrderState
from 1 (Open) to 3 (Committed)
[
{ "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:
{
"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 Shop’s Manifest
:
Adding a new
ShippingRegion
with aPriceModifier
[
{
"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 Listing
:
Removing the first two images from the
ListingMetadata
Adding two new images to the end of the array
[
{ "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¶
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
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 SubscriptionPushRequest for more.
CBOR References¶
Shop datastructures¶
Shop is a static map with the following fields:
Field |
Required |
Type |
---|---|---|
SchemaVersion |
Yes |
uint |
Manifest |
Yes |
|
Accounts |
Yes |
|
Listings |
Yes |
|
Tags |
Yes |
|
Orders |
Yes |
|
Inventory |
Yes |
Accounts is a dynamicmap with the following fields:
Key Type |
Value Type |
---|---|
Listings is a dynamicmap with the following fields:
Key Type |
Value Type |
---|---|
uint |
Tags is a dynamicmap with the following fields:
Key Type |
Value Type |
---|---|
text |
Orders is a dynamicmap with the following fields:
Key Type |
Value Type |
---|---|
uint |
Inventory is a dynamicmap with the following fields:
Key Type |
Value Type |
---|---|
uint |
uint |
SignatureSize is a constant with value: 65
PublicKeySize is a constant with value: 33
HashSize is a constant with value: 32
EthereumAddressSize is a constant with value: 20
Signature is a constrained bytes
PublicKey is a constrained bytes
Hash is a constrained bytes
EthereumAddress is a constrained bytes
Uint256 is a constrained bytes
Uint32 is a constrained uint
ChainAddress is a static map with the following fields:
Field |
Required |
Type |
---|---|---|
ChainID |
Yes |
uint |
Address |
Yes |
Payee is a static map with the following fields:
Field |
Required |
Type |
---|---|---|
Address |
Yes |
|
CallAsContract |
Yes |
bool |
PayeeMetadata is a static map with the following fields:
Field |
Required |
Type |
---|---|---|
CallAsContract |
Yes |
bool |
Manifest is a static map with the following fields:
Field |
Required |
Type |
---|---|---|
ShopID |
Yes |
|
Payees |
Yes |
Map[uint] => Map[EthereumAddress] => PayeeMetadata |
AcceptedCurrencies |
Yes |
Map[uint] => Map[EthereumAddress] => null |
PricingCurrency |
Yes |
|
ShippingRegions |
No |
Map[text] => ShippingRegion |
ShippingRegion is a static map with the following fields:
Field |
Required |
Type |
---|---|---|
Country |
Yes |
text |
PostalCode |
Yes |
text |
City |
Yes |
text |
PriceModifiers |
No |
Map[text] => PriceModifier |
PriceModifier is a static map with the following fields:
Field |
Required |
Type |
---|---|---|
ModificationPrecents |
No |
|
ModificationAbsolute |
No |
ModificationAbsolute is a static map with the following fields:
Field |
Required |
Type |
---|---|---|
Amount |
Yes |
|
Plus |
Yes |
bool |
Listing is a static map with the following fields:
Field |
Required |
Type |
---|---|---|
ID |
Yes |
uint |
Price |
Yes |
|
Metadata |
Yes |
|
ViewState |
Yes |
|
Options |
No |
Map[text] => ListingOption |
StockStatuses |
No |
Array of ListingStockStatus |
ListingMetadata is a static map with the following fields:
Field |
Required |
Type |
---|---|---|
Title |
Yes |
text |
Description |
Yes |
text |
Images |
No |
Array of text |
ListingOption is a static map with the following fields:
Field |
Required |
Type |
---|---|---|
title |
Yes |
text |
variations |
No |
Map[text] => ListingVariation |
ListingVariation is a static map with the following fields:
Field |
Required |
Type |
---|---|---|
VariationInfo |
Yes |
|
PriceModifier |
No |
|
SKU |
No |
text |
ListingViewState is an enumeration with the following values:
Name |
Value |
comment |
---|---|---|
Unspecified |
0 |
hidden |
Published |
1 |
published |
Deleted |
2 |
soft-deleted |
ListingStockStatus is a static map with the following fields:
Field |
Required |
Type |
---|---|---|
VariationIDs |
Yes |
Array of text |
InStock |
No |
bool |
ExpectedInStockBy |
No |
text |
Tag is a static map with the following fields:
Field |
Required |
Type |
---|---|---|
Name |
Yes |
text |
ListingIDs |
Yes |
Array of uint |
Account is a static map with the following fields:
Field |
Required |
Type |
---|---|---|
KeyCards |
Yes |
Array of PublicKey |
Guest |
Yes |
bool |
Order is a static map with the following fields:
Field |
Required |
Type |
---|---|---|
ID |
Yes |
uint |
Items |
Yes |
Array of OrderedItem |
State |
Yes |
|
InvoiceAddress |
No |
|
ShippingAddress |
No |
|
CanceledAt |
No |
text |
ChosenPayee |
No |
|
ChosenCurrency |
No |
|
PaymentDetails |
No |
|
TxDetails |
No |
OrderedItem is a static map with the following fields:
Field |
Required |
Type |
---|---|---|
ListingID |
Yes |
uint |
VariationIDs |
No |
Array of text |
Quantity |
Yes |
OrderState is an enumeration with the following values:
Name |
Value |
comment |
---|---|---|
Unspecified |
0 |
invalid state |
Open |
1 |
open to being changed |
Canceled |
2 |
canceled |
Committed |
3 |
items frozen |
PaymentChosen |
4 |
currency and payee chosen |
Unpaid |
5 |
details for payment created |
Paid |
6 |
payment completed/received |
AddressDetails is a static map with the following fields:
Field |
Required |
Type |
---|---|---|
Name |
Yes |
text |
Address1 |
Yes |
text |
Address2 |
No |
text |
City |
Yes |
text |
PostalCode |
Yes |
text |
Country |
Yes |
text |
EmailAddress |
Yes |
text |
PhoneNumber |
No |
text |
PaymentDetails is a static map with the following fields:
Field |
Required |
Type |
---|---|---|
PaymentID |
Yes |
|
Total |
Yes |
|
ListingHashes |
Yes |
Array of Hash |
TTL |
Yes |
uint |
ShopSignature |
Yes |
OrderPaid is a static map with the following fields:
Field |
Required |
Type |
---|---|---|
TxHash |
No |
|
BlockHash |
Yes |