The ODD SDK is a local-first toolkit that helps developers build systems that store data in a secure, reliable, and decentralised way. The focus is on the user, providing them with sufficient authority while still being convenient.
ODD stands for Open Distributed Data. We want our data accessible from anywhere and distributed, meaning it can live partially or fully in multiple places. Ideally, as much of this data as possible lives on user owned devices and communication of this data will be done in a decentralised, peer-to-peer fashion.
We also prefer not to rely on centralised services when providing users and devices with the needed authority. When a user starts using a new device, their existing device should authorise the new one without any intervention from a central service. This can be done by giving the new device one or more tickets that can be verified on the spot.
Rather than putting everything in a closed system, ODD puts everything out in the open. This means encryption is used to keep data private. Additionally, these encryption keys and authorisation tickets enable us to escape from passwords.
The ODD SDK relies on open protocols, these include the WNFS protocol for data management, and UCAN to provide users and devices with the necessary authorisation.
ts-odd is written in Typescript, a Javascript dialect. The resulting code should be usable in any modern Javascript environment that supports the Web Crypto API and Web Assembly. It uses the Rust implementations of WNFS and UCAN, which compile to Web Assembly.
Various other pieces are layered on top of these core protocols and assumptions, these are called "components" in ts-odd. These allow us to adapt to various technologies and environments. We'll start with the components closest to the data and go from there. These are:
- Depot
- Account
- Identifier & Agent
- Authority
- Channel
- DNS
- Storage
- Manners
The following graph shows a rough overview how the components relate:
flowchart LR;
Identifier --> Account --> Agent --> AS(((Account service))):::service
FS[(File System)]:::subject --> Depot
FS[(File System)]:::subject --> Manners
FS[(File System)]:::subject --> DNS
Authority --> Channel
Storage --> Authority
Depot <--> Storage
Account <--> Storage
Agent --> Storage
classDef component fill:#e3d2c4,stroke:#caaa90
classDef service fill:#e7d0ea,stroke:#cc9bd2
classDef subject fill:#f3dee0,stroke:#dfa6ab
class Account,Agent,Authority,Channel,Depot,DNS,Identifier,Manners,Storage component
These are only some of the relationships, but they should give you a rough idea of some important concepts in the SDK. Starting from the top, we have the Identifier
component that provides an authorisation ticket to the Account
component, which in turn makes another ticket and passes it to some external account service. Note that this is just an example, it may not use an external service.
Next, we have the 'Storage' component, which can be thought of as session storage. In the default implementation the (file system) data is stored here, but also the authorisation tickets and the cryptographic keys. Then the authority component can redistribute those authorisation tickets if granted by the user, which happens over a channel.
There are some other components, such as DNS
and Manners
, which are somewhat less important, but more on those later.
All of these components are grouped together in a Program
instance, which will be your main interaction point. It's an artificial environment with the components you've chosen to use. For example, you could have a program that stores session data in memory, or one that stores it in indexedDB.
The 'WNFS' file system that holds the user's data consists of IPLD blocks. These blocks make up the content-addressable web, another core idea to the ODD stack. A content address gives us a fingerprint for our data. Whenever the data changes, the fingerprint changes. This means that when you provide a specific content address, you will always get back the same data. This in turn means the storage associated with the file system is tamper-proof, setting us up with an ideal primitive for data syncing.
This component is responsible for retrieving and storing these IPLD blocks. On the lowest level, this means for any given bytes (data) give me back a CID (content address). And vice versa, when I present a CID, give me back the bytes for it.
From a high level, this unlocks the larger part of data syncing: the ability to transfer our blocks to a remote source.
Learn more about content addressing.
Usually to push the data to a remote endpoint, you will need some sort of account system, otherwise anyone could fill up the external storage space which costs money. The user accounts will be represented through authorisation, UCANs, our authorisation tickets.
This ODD SDK program has a UCAN store, a repository of "authorisation tickets" collected from various sources. The account system is one of those sources. It can also access that UCAN store and then use it to authorise various actions.
This may also tie into the other part of data syncing: taking the file system's root CID (aka. data root) and storing it somewhere the other devices can reach it.
The account system also involves the Identifier
and Agent
components. The Identifier
signifies one of the user's identifiers and the Agent
delegates to external services. Both of these use decentralised identifiers (DIDs). These DIDs could, for example, be derived from a passkey, a blockchain wallet or a CryptoKeyPair
from the Web Crypto API.
The reason we have two components here is so the Identifier
doesn't need to sign every time. This is useful, for example, with passkeys or blockchain wallets where you would get a popup each time you'd need to sign something.
Typically the Agent
will use a temporary session key pair and the Identifier
is the more permanent identifier. Example flow: Identifier delegates to agent, agent contacts remote account service, account service issues UCANs addressed to identifier. Those UCANs are then used throughout the SDK to check for capabilities, etc.
The storage DID used for the file system is always the identifier DID.
This component is responsible for providing and requesting authority, which technically means providing and requesting UCANs and file system secrets (access keys).
Channels are used for authority exchange and to communicate file system changes to a user's other devices. This component is used to create a channel. It serves as the public channel for the AWAKE protocol, which bootstraps a secure session. Using this protocol we can establish point-to-point communication that is fully secure and encrypted.
Determines how to do DNS queries.
A key-value storage abstraction responsible for storing various pieces of session data, such as UCANs and crypto keys (depending on other components used).
The manners component allows you to tweak various behaviours of an ODD program, such as logging and file system hooks (eg. what to do after a new file system is created).