Skip to main content

ADR-002: IPLD Objects With JOSE (WIP)



Implement Objects based on IPLD which support JOSE operations.


Objects on the Sonr network provide a way for users and applications to persist data. The problem with modern software architectures is that the security of user data is not guaranteed—a problem Sonr aims to solve with the help of JavaScript Object Signing and Encryption (JOSE).

Additionally, today’s software stack is not built with interoperability in mind. An entire layer must be added to any application in order to support communication with other applications. IPLD provides a standard and decentralized way for any motor node to make use of the data stored on Sonr.

The combining of these technologies begets a system in which user data is always encrypted and can never be owned by one application, even when that application created it.


The content of this ADR is based on the works of ADR-001 as well as a few core technologies—all of which should be well understood before reading ADR-002. Here is a list of resources.


The objective of ADR-002 is to define the way in which Sonr manages the creation and access of data on its network. A proper solution must have these attributes.

  • Enable motor nodes to store data on behalf of a user.
  • Enable devices which belong to the same user to access the same data.
  • Enable certain data to be shared with other users.
  • Allow for revoking access to both devices and other users.
  • Maintain that all data is encrypted in transit and at rest.

Changes From ADR-001

To accommodate the features proposed by ADR-002, a few changes to terminology will be made from ADR-001.

Rename Object to Schema

Object in ADR-001 represents a type definition in Sonr. This name is misleading as it creates ambiguity between the type definition and the data which is of that type. With the addition of IPLD, the term Schema will better represent what today’s Object truly is while creating consistency with the libraries we employ.

Client Side Encryption with JOSE

A core principle of Sonr is for private user data never to pass unencrypted through the network. To achieve this, the motor node must be responsible for encrypting and decrypting all data. This section describes how a user may securely store data on Sonr and maintain the ability to access that data from any motor associated with his/her account.


In order for encrypted data to be accessed from multiple devices, there must be a shared key for each piece of data which will be accessible to each motor. The high-level process for storing some data looks like this. \ Key Creation

The JWK used during the encryption process is determined by the access levels for the data. As the owner of data, your vault will contain a map of CID to map of PK to encrypted JWK—map[CID]map[PK]JWK; where CID is the CID of the object, PK is the public key of a member who has access, and JWK is the JWK used to encrypt the data (encrypted with PK).


  1. An object is created using a Schema—this is what will be stored.
  2. The motor encrypts the data using its shared key for private data. If the data is meant to be shared with a party, a key specific to that party is used.
    1. Any property in the data that is represented as a separate Schema will be encrypted separately and included with the POST request.
    2. Each encrypted schema will also have a corresponding CID. Because CIDs are generated in a predictable way, this will be computed by the motor.
  3. The motor _then creates a copy of the key it generated and encrypts it with each public key in the root-level DID. This is ultimately what allows another _motor to decrypt the data.
  4. A request can now be crafted to Highway with the encrypted data and set of encrypted keys. 3. The request should include each encrypted schema along with the list of encrypted shared keys.


  1. When a request is received, each object is added to IPFS.
  2. The CID for each object will be verified, creating an error if one does not match.
  3. If successful, each CID is added to a record on-chain.


The process for decrypting data becomes simple with the above provisions. The motor will first obtain the shared key—accessible via the vault (more on that later)—which will be used to encrypt data fetched through IPFS. The entire process is as follows.

  1. Fetch the shared key and decrypt it.
    1. The shared key will have already been added to the vault by the uploading device. This process is outlined in the next section.
  2. Through IPFS, fetch the relevant data via CID. For each referenced CID in the result, fetch that CID. Continue this process until all objects have been recovered.
  3. Decrypt each object using the shared key.
  4. Compose the original schema by replacing the IPLD Link with the referenced object.

Revoking Access

If any device or party is to have its access revoked, a new key must be used for the next write to that document.

Adding Verification Methods

The process of securing user data starts with the verification method. Simply put, verification methods are a member of the DID Document which contain the public keys used for authentication. New verification methods can be added in order for a user to authenticate with new or different keys. Later sections will detail how these methods are used to create JWKs for secure data storage.

The important thing to note about Verification Methods in ADR-002 is that the process for adding a Verification Method must now include encrypting all shared keys again with the new method’s public key.

WhoIs Definition

type WhoIs struct {
// Alias is the list of registered `alsoKnownAs` identifiers of the User or Application
Alias []*Alias `protobuf:"bytes,1,rep,name=alias,proto3" json:"alias,omitempty"`

// Owner is the top level DID of the User or Application derived from the multisignature wallet.
Owner string `protobuf:"bytes,2,opt,name=owner,proto3" json:"owner,omitempty"`

// DIDDocument is the bytes representation of DIDDocument within the WhoIs. Initially marshalled as JSON.
DidDocument []byte `protobuf:"bytes,3,opt,name=did_document,json=didDocument,proto3" json:"did_document,omitempty"`

// Credentials are the biometric info of the registered name and account encoded with public key
Controllers []string `protobuf:"bytes,4,rep,name=controllers,proto3" json:"controllers,omitempty"`

// Type is the kind of the entity. Possible values are: "user", "application"
Type WhoIsType `protobuf:"varint,5,opt,name=type,proto3,enum=sonrio.sonr.registry.WhoIsType" json:"type,omitempty"`

// Timestamp is the time of the last update of the DID Document
Timestamp int64 `protobuf:"varint,6,opt,name=timestamp,proto3" json:"timestamp,omitempty"`

// IsActive is the status of the DID Document
IsActive bool `protobuf:"varint,7,opt,name=is_active,json=isActive,proto3" json:"is_active,omitempty"`

DID Document Verification Definition

// Connected Motors Webauthn credentials get stored as verificationMethod
"verificationMethod": [
// Set to Motor Nodes Wallet Address
"controller": "did:snr:123",

// Id of Key set to unique value and operating system/architecture
"id": "did:snr:123#ios-arm64-1",

// JWK generated from WebAuthN Credential
"publicKeyJwk": {
"crv": "P-256",
"kty": "EC",
"x": "UANQ8pgvJT33JbrnwMiu1L1JCGQFOEm1ThaNAJcFrWA=",
"y": "UWm6q5n1iXyeCJLMGDInN40bkkKr8KkoTWDqJBZQXRo="
"type": "JsonWebKey2020"


This property is utilized for storing the individual Motor WebAuthn credentials. This mechanism is put into place to associate users by individual devices opposed to strictly an account based structure.


Currently, the controller represents the set of DIDs associated with the top-level document for a User. In order for the controller to be valid an accompanying entry must be present in the verificationMethod and must conform to the FIDO2 WebAuthn specification.

Identity Verification

Each identity is unique to the user and derived from the public key credential given by Webauthn. The Json Web Key (JWK) must be derived from the Credential provided by the User registration operation. When a user is registered on Sonr. A Credential from the authenticated device will be provided, which derives from the JWK. These are unique per device and will be included within the “VerificationMethod '' array in the did model. This relationship is a 1 to many as to accommodate many devices registered to a single Identity.

Identity Verification Flow Diagrams

Changes to interfaces for Object encryption

The following methods need to be added to the Highway IPFS proto implementation. Object encryption within the IPFS specification relies on JOSE encryption specification. Below are the data models used from encrypting object data to be stored within an IPFS node: see here


* AddSignedObject
* Implement IPLD Codec
* DAG Mechanism
* AddEncryptedObject
* Follow Secret Path


Objects stored as a node within the highway IPFS storage need to be encrypted using JOSE JWK.

See section DID METHODS


Objects signed with a public key credential will then be encrypted using their private key on the given Motor Node and persisted to our IPFS server. Objects will first have their “Schema” looked up and stored, if there is an error in encoding the data to the given schema (type definition) an error of “internal error” or 500 code will be sent back in the message response to the user.

IPLD Codec

DAG-CBOR is a codec that implements the IPLD Data Model as a subset of CBOR, plus some additional constraints for hash consistent representations. DAG-CBOR also adds a "link" type using a CBOR tag, to bring it in line with the IPLD Data Model.

Node Pathing

Paths are composed of a series of segments, and each segment is an instruction on how to navigate deeper into the filesystem. With filesystems, each step is over a "directory" and leads you to either a "file" or another "directory"; for IPLD, each step is over a "node" and leads you to another "node"!

IPLD Object Schema

IPLD represents data types which can be stored in IPFS nodes as “Kinds” the following are types which “Kinds” support:

  • boolean
  • integer
  • float
  • map
  • list
  • string
  • null
  • bytes
  • link

Kinds in IPLD are similar to data types supported within “JSON” but also add “bytes” and “link” where link maps to “CID” type.

DSL Representation of Object Schema

Node builders allow you to declare schema within an object representation that then can be transformed to various types. Builders then can be represented in DSL which is an declarative syntax for IPLD schemas similar to JSON. The following is a method stub for creating an object schema for later usage:

func (i *IPFSProtocol) PutObjectSchema(doc *ot.ObjectDoc) (*cid.Cid, error)

The above method stores an DSL representation of a defined object schema with the following relationship denoting a property of the persistent schema

Label: string
Type: Kind

This object will be stored within a aggregate (array structure) within the ObjectDoc and should be iterated over and stored within a node builder, then serialized to a byte representation.


DID Methods for CBOR-JOSE support


  • Authenticate
  • CreateJWS
  • DecryptJWE
  • EnctyptJWE
  • VerifyJWS
  • VerifyJWS


We must verify the controller’s (device) credential and verify with the controller’s verification method by utilizing the public key method within the verification object. Once the given key has been verified, the requester will be given a jwt, and be then verified as authenticated.


Generates a JSON web signature object composed of a multi signature key.

"link": {
"/": "bafybeig6xv5nwphfmvcnektpnojts33jqcuam7bmye2pb54adnrtccjlsu"
"payload": "AXASIN69ets85WVE0ipva5M5b2mAqAZ8LME08PeAG2MxCSuV",
"signatures": [
"protected": "eyJhbGciOiJFUzI1NksifQ",
"signature": "SiYGXW7Yi-KxbpIlLNmu0lEhrayV7ypaAC49GAcQ_qpTstZW89Mz6Cp8VlUEX-qVsgYjc-9-1zvLcDYlxOsr1g"
"protected": "eyJhbGciOiJFUzI1NksifQ",
"signature": "Q8PdTE5A5N3a0ktO2wNdUymumHlSxNF9Si38IvzsMaSZC63yQw-bJNpKf-UeJFPH7cDzY7jLg2G_viejp7NqXg"


We can compute SHA-256 representation if secret is present within the generated token.

Encoded JOSE types (IPLD schema)

type EncodedSignature struct {
header optional {String:Any}
protected optional Bytes
signature Bytes

type EncodedRecipient struct {
encrypted_key optional Bytes
header optional {String:Any}

type EncodedJWE struct {
aad optional Bytes
ciphertext Bytes
iv optional Bytes
protected optional Bytes
recipients [EncodedRecipient]
tag optional Bytes
unprotected optional {String:Any}

type EncodedJWS struct {
payload optional Bytes
signatures [EncodedSignature]

Interaction Methods for Highway to Motor communication

The following are interface methods for communication between our Highway and Motor nodes


User authenticates a Registered Application on Sonr with their DID Based Multisignature key for all their devices. Creates a new Bucket inside the User bucket for the newly provisioned Application.


Users map the new data for a specific type definition presented in the UI, and push the updated data to the corresponding application in their Bucket. This utilizes the JWE process in order to encrypt data from the User end.


User specifies which application data stream to begin reading for data. The returned channel is a listenable stream or callback depending on Device architecture.


This method allows motor based applications to link an additional WebAuthN credential to their top-level DID Document.


Establishes a linkage between two “peer” nodes to establish another node in the network.


This method pulls Application Specific buckets type definitions, functions, etc. In order to render the payloads onto the frontend UI.


This method begins the request process for reading the individual object values of another User's app bucket. Provisioned users will automatically get access (if they called RequestBucket already) and unprovisioned users will send the RequestBucket to the corresponding peer.


This method is utilized for accessing another user's application specific data holistically. This would be utilized for full access to a peers App Data config


This method is utilized for responding to a request from another user from the mailbox folder in their User specific bucket.


  • Show updated WhoIs model
  • Describe interaction between IPFS (or ambiguous data storage layer) and IPLD model.
    • The JSON API that is Highway will need a codec to encode posted data before storage in IPFS
    • Need to consider how this will work with the encryption. How might selectors and paths of IPLD traverse encrypted data? Can IPLD encoding be done in motor?
  • IPLD Object definition
  • Describe new process for adding verification methods
  • Required methods to be added to highway (for use in motor lib)
    • Get all symmetric keys
    • When adding a new verification method, this data will be required so that all existing symmetric encryption keys can be re-encrypted for the new device.