In the W3 protocol user owned (name)space represents a data storage primitive that can be managed using W3 storage protocol defined here. Storage protocol allows space owners to manage state across compatible storage provider services using defined set of UCAN capabilities. Use of UCAN authorization system enables space access to be shared by delegating corresponding capabilities to desired audience.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC2119.
The base storage layer of the space is Content Addressed Content Archive files. In practice this means that user wishing to store files or directories of files needs produce an IPLD DAG in UnixFS format and then encoded into one or multiple [Content Archive]s that can be stored using W3 storage protocol.
Large DAGs get "sharded" across multiple [Content Archive]s and stored individually to meet potential size limits of the storage provider.
Separately upload/
protocol can be utilized allowing user to create standalone entities (files, directories) representing entry points to the DAGs contained by the [Content Archive]s. E.g. when storing a file or a directory UnixFS root [CID] and archives are captured using upload/add
capability allowing viewer to effectively fetch and re-assemble it.
There are several distinct roles that principals may assume in this specification:
Name | Description |
---|---|
Principal | The general class of entities that interact with a UCAN. Identified by a DID that can be used in the iss or aud field of a UCAN. |
Agent | A Principal identified by [did:key ] identifier, representing a user in an application. |
Issuer | A principal delegating capabilities to another principal. It is the signer of the UCAN. Specified in the iss field of a UCAN. |
Audience | Principal access is shared with. Specified in the aud field of a UCAN. |
A namespace, often referred as a "space", is an owned resource that can be shared. It corresponds to a unique asymmetric cryptographic keypair and is identified by a [did:key
] URI. The store/
and upload/
capabilities can be used to manage content stored in the given space at given storage provider.
Content Archive often referred as CAR is a primary primitive for storing shards of the content in the space.
Capabilities under store/*
namespace can be used to manage CARs that provider is storing and serving on behalf of the space.
type StoreCapability union {
StoreAddCapability "store/add"
StoreGetCapability "store/get"
StoreListCapability "store/list"
StoreRemoveCapability "store/remove"
} representation inline {
discriminantKey "can"
}
The audience of the invocation (aud
field) MUST be the provider DID of the storage provider implementing this protocol.
The subject of the invocation (with
field) MUST be the DID of the target MUST be target space.
The nb.link
field of the invocation MUST be an IPLD Link to the desired CAR. Link MUST have Content Addressable Archive (CAR) 0x0202
codec code. It is RECOMMENDED to support SHA2-256 multihash code 0x12
. Implementers are MAY choose to support other additional hashing algorithms.
Authorized agent MAY invoke store/add
capability on the space subject (with
field) to request that provider (aud
field) store and serve CAR on their behalf.
Invoking store/add
capability for a CAR that is already added to space SHOULD be a noop.
type StoreAddCapability struct {
with SpaceDID
nb StoreAdd
}
type StoreAdd struct {
link &ContentArchive
size Int
origin optional &ContentArchive
}
type ContentArchive = bytes
type SpaceDID = DID
type DID = string
Capability invocation MUST specify Content archive Identifier.
Capability invocation MUST set nb.size
field to the byte size of the content archive.
Status: Deprecated 🛑
Field was intended for establish causal order, but proved impractical.
Capability invocation MAY set optional nb.origin
field to a causally related CAR like a previous shard of the content DAG.
{
"v": "0.9.1",
"iss": "did:key:z6Mkk...xALi",
"aud": "did:web:web3.storage",
"att": [
{
can: "store/add",
with: "did:key:zAl..1ce",
nb: {
link: { "/": "bag...7ldq" },
size: 42_600_000
}
}
],
"prf": [{ "/": "bafy...prf1" }],
"exp": 1685602800,
"s": {
"/": {
"bytes": "7aEDQJbJqmyMxTxcK05XQKWfvxG+Tv+LWCJeE18RSMnciCZ/RQ21U75LA0uFSvIjdqnF5RaauZTE8mh2ZYMBBejdJQ4"
}
}
}
Provider MUST issue signed receipt containing StoreAddResult
.
type StoreAddResult union {
StoreAddSuccess "ok"
StoreAddFailure "error"
} representation keyed
type StoreAddSuccess union {
StoreAddDone "done"
StoreAddPending "upload"
} representation inline {
discriminantKey "status"
}
type StoreAddDone struct {
with SpaceDID
link &ContentArchive
allocated Int
}
type StoreAddPending struct {
with SpaceDID
link &ContentArchive
url URL
headers HTTPHeaders
allocated Int
}
type URL = string
type HTTPHeaders {String: String}
type StoreAddFailure struct {
message String
}
Capability provider MUST succeed request with a "done"
status if provider is able to store requested CAR on user behalf right-away. In other words provider already has addressed CAR file.
Capability provider MUST set allocated
field to number of bytes it had no allocate for the CAR, which MUST be either equal to nb.size
of the invocation or 0
if no additional memory had to be allocated.
Capability provider MUST succeed request with a "upload"
status if it is able to allocate memory for the requested CAR. It MUST set url
field to an HTTP PUT endpoint where addressed CAR can be uploaded.
Provider MUST set headers
field to the set of HTTP headers that uploading agent MUST set on an HTTP PUT request.
HTTP PUT endpoint set in url
field MUST verify that uploaded bytes do correspond to the addressed CAR and specified size
.
Capability provider MUST set allocated
field to number of bytes it had no allocate for the CAR, which MUST be either equal to nb.size
of the invocation or 0
if no additional memory had to be allocated.
Capability provider SHOULD fail invocation if the subject space has not been provisioned with the provider.
Capability provider SHOULD fail invocation if the subject space has not been provisioned with enough storage capacity to store requested archive.
Authorized agent MAY invoke store/get
capability on the space subject (with
field) to query a state of the of the specified CAR (nb.link
field) of the replica held by the provider (aud
field).
type StoreGetCapability struct {
with SpaceDID
nb StoreGet
}
type StoreGet struct {
link &ContentArchive
}
Capability invocation MUST specify Content archive Identifier.
{
"v": "0.9.1",
"iss": "did:key:z6Mkk...xALi",
"aud": "did:web:web3.storage",
"att": [
{
can: "store/get",
with: "did:key:zAl..1ce",
nb: {
link: { "/": "bag...7ldq" },
}
}
],
"prf": [{ "/": "bafy...prf1" }],
"exp": 1685602800,
"s": {
"/": {
"bytes": "7aEDQJbJqmyMxTxcK05XQKWfvxG+Tv+LWCJeE18RSMnciCZ/RQ21U75LA0uFSvIjdqnF5RaauZTE8mh2ZYMBBejdJQ4"
}
}
}
Capability provider MUST issue signed receipt containing StoreGetResult
.
type StoreGetResult struct {
StoreGetSuccess "ok"
StoreGetFailure "error"
} representation keyed
Capability provider MUST issue StoreGetSuccess
result for every content archive that has been added to the space.
type StoreGetSuccess {
link &ContentArchive
size Int
# deprecated
origin optional &ContentArchive
}
Capability provider MUST issue StoreGetFailure
result for every content archive that either has not been added or was since removed at the time of the invocation.
type StoreGetFailure {
message string
}
Authorized agent MAY invoke store/remove
capability to remove content archive from the subject space (with
field).
type StoreRemoveCapability struct {
with SpaceDID
nb StoreRemove
}
type StoreRemove struct {
link &ContentArchive
}
Capability invocation MUST specify desired Content archive Identifier.
{
"v": "0.9.1",
"iss": "did:key:z6Mkk...xALi",
"aud": "did:web:web3.storage",
"att": [
{
can: "store/remove",
with: "did:key:zAl..1ce",
nb: {
link: "bag...7ldq",
}
}
],
"prf": [{ "/": "bafy...prf1" }],
"exp": 1685602800,
"s": {
"/": {
"bytes": "7aEDQJbJqmyMxTxcK05XQKWfvxG+Tv+LWCJeE18RSMnciCZ/RQ21U75LA0uFSvIjdqnF5RaauZTE8mh2ZYMBBejdJQ4"
}
}
}
Capability provider MUST issue signed receipt containing StoreRemoveResult
.
type StoreRemoveResult struct {
StoreRemoveSuccess "ok"
StoreRemoveFailure "error"
} representation keyed
Capability provider MUST issue StoreRemoveSuccess
after it unlinked the archive from the space.
Capability provider MUST set size
field to the number of bytes that were freed from space.
Capability provider MUST issue StoreRemoveSuccess
even when specified archive is not in space. In that case it MUST set size
to 0
.
type StoreRemoveSuccess {
size Int
}
Authorized agent MAY invoke store/list
capability on the space subject (with
field) to list CARs added to it at the time of invocation.
type StoreListCapability struct {
with SpaceDID
nb StoreList
}
type StoreList struct {
cursor optional &string
size optional int
pre optional bool
}
The optional nb.cursor
MAY be specified in order to paginate over the list of the added CARs.
The optional nb.size
MAY be specified to signal desired page size, that is number of items in the result.
The optional nb.pre
field MAY be set to true
to request a page of results preceding cursor. If nb.pre
is omitted or set to false
provider MUST respond with a page following the specified nb.cursor
.
{
"v": "0.9.1",
"iss": "did:key:z6Mkk...xALi",
"aud": "did:web:web3.storage",
"att": [
{
can: "store/list",
with: "did:key:zAl..1ce",
nb: {
size: 40,
cursor: 'cursor-value-from-previous-invocation',
}
}
],
"prf": [{ "/": "bafy...prf1" }],
"exp": 1685602800,
"s": {
"/": {
"bytes": "7aEDQJbJqmyMxTxcK05XQKWfvxG+Tv+LWCJeE18RSMnciCZ/RQ21U75LA0uFSvIjdqnF5RaauZTE8mh2ZYMBBejdJQ4"
}
}
}
Capability provider MUST issue signed receipt containing StoreListResult
.
type StoreListResult union {
StoreListSuccess "ok"
StoreListFailure "error"
} representation keyed
Capability provider MUST issue StoreListSuccess
result containing page of entries added to the space.
type StoreListSuccess struct {
cursor optional string
before optional string
after optional string
size int
results [StoreListItem]
}
type StoreListItem struct {
link &ContentArchive
size int
origin optional &ContentArchive
}
type StoreListFailure struct {
message string
}
Capabilities under upload/*
namespace can be used to manage list of top level content entries. While not required, it is generally assumed that user content like file will be turned into UnixFS DAG packed and stored in space as one or more content archives. The root of the DAG is then added to the upload list.
type UploadCapability union {
UploadAddCapability "upload/add"
UploadGetCapability "upload/get"
UploadListCapability "upload/list"
UploadRemoveCapability "upload/remove"
} representation inline {
discriminantKey "can"
}
Authorized agent MAY invoke upload/add
capability on the space subject (with
field) to request that provider (aud
field) include content identified by nb.root
in the list of content entries for the space.
It is expected that CARs containing content are stored in the space using store/add
capability. Provider MAY enforce this invariant by failing invocation or choose to succeed invocation but fail to serve the content when requested.
⚠️ Behavior of callingupload/add
with a sameroot
and differentshards
is not specified by the protocol. w3up reference implementation allows such invocations and updatesshards
to union of all shards across invocations.
type UploadAddCapability struct {
with SpaceDID
nb Upload
}
type Upload struct {
root &any
shards [&ContentArchive]
}
The nb.root
field MUST be set to the IPLD Link of the desired content entry.
The nb.shards
field MUST be set to the list of IPLD Links for the Content Archives containing the upload entry.
{
"v": "0.9.1",
"iss": "did:key:z6Mkk...xALi",
"aud": "did:web:web3.storage",
"att": [
{
can: "upload/add",
with: "did:key:zAl..1ce",
nb: {
root: { "/": "bafy...k3ve" },
shards: [{ "/": "bag...7ldq" }]
}
}
],
"prf": [{ "/": "bafy...prf1" }],
"exp": 1685602800,
"s": {
"/": {
"bytes": "7aEDQJbJqmyMxTxcK05XQKWfvxG+Tv+LWCJeE18RSMnciCZ/RQ21U75LA0uFSvIjdqnF5RaauZTE8mh2ZYMBBejdJQ4"
}
}
}
Capability provider MUST issue signed receipt containing UploadAddResult
.
type UploadAddResult union {
UploadAddSuccess "ok"
UploadAddFailure "error"
} representation keyed
type UploadAddSuccess struct {
root &any
shards [&ContentArchive]
}
type UploadAddFailure struct {
message string
}
Authorized agent MAY invoke upload/get
capability on the space subject (with
field) to query a state of the of the specified upload entry.
type UploadGetCapability struct {
with SpaceDID
nb UploadGet
}
type UploadGet struct {
root &any
}
The nb.root
field MUST be set to the IPLD Link of the desired content entry.
{
"v": "0.9.1",
"iss": "did:key:z6Mkk...xALi",
"aud": "did:web:web3.storage",
"att": [
{
can: "upload/get",
with: "did:key:zAl..1ce",
nb: {
root: { "/": "bafy...ro0t" },
}
}
],
"prf": [{ "/": "bafy...prf1" }],
"exp": 1685602800,
"s": {
"/": {
"bytes": "7aEDQJbJqmyMxTxcK05XQKWfvxG+Tv+LWCJeE18RSMnciCZ/RQ21U75LA0uFSvIjdqnF5RaauZTE8mh2ZYMBBejdJQ4"
}
}
}
Capability provider MUST issue signed receipt containing UploadGetResult
.
type UploadGetResult struct {
UploadGetSuccess "ok"
UploadGetFailure "error"
} representation keyed
Capability provider MUST issue UploadGetSuccess
result for the upload entry that has been added to the space.
type UploadGetSuccess {
link &any
shards [&ContentArchive]
}
Capability provider MUST issue UploadGetFailure
result if content entry has not been added or was since removed from space at the time of the invocation.
type UploadGetFailure {
message string
}
Authorized agent MAY invoke upload/remove
capability to remove upload entry from the list in the specified space (with
field).
⚠️ Please note that removing upload entry MUST NOT remove content archives containing contain.
type UploadRemoveCapability struct {
with SpaceDID
nb UploadRemove
}
type UploadRemove struct {
root &any
}
The nb.root
field MUST be set to the IPLD Link of the desired content entry.
{
"v": "0.9.1",
"iss": "did:key:z6Mkk...xALi",
"aud": "did:web:web3.storage",
"att": [
{
can: "upload/remove",
with: "did:key:zAl..1ce",
nb: {
root: { "/": "bafy...ro0t" },
}
}
],
"prf": [{ "/": "bafy...prf1" }],
"exp": 1685602800,
"s": {
"/": {
"bytes": "7aEDQJbJqmyMxTxcK05XQKWfvxG+Tv+LWCJeE18RSMnciCZ/RQ21U75LA0uFSvIjdqnF5RaauZTE8mh2ZYMBBejdJQ4"
}
}
}
Capability provider MUST issue signed receipt containing UploadRemoveResult
.
type UploadRemoveResult struct {
UploadRemoveSuccess "ok"
UploadRemoveFailure "error"
} representation keyed
type UploadRemoveSuccess {
link &any
shards [&ContentArchive]
}
type UploadGetFailure {
message string
}
Authorized agent MAY invoke upload/list
capability on the space subject (with
field) to list upload entries at the time of the invocation.
type UploadListCapability struct {
with SpaceDID
nb UploadList
}
type UploadList struct {
cursor optional &string
size optional int
pre optional bool
}
The optional nb.cursor
MAY be specified in order to paginate over the list of upload entries.
The optional nb.size
MAY be specified to signal desired page size, that is number of items in the result.
The optional nb.pre
field MAY be set to true
to request a page of results preceding cursor. If nb.pre
is omitted or set to false
provider MUST respond with a page following the specified nb.cursor
.
{
"v": "0.9.1",
"iss": "did:key:z6Mkk...xALi",
"aud": "did:web:web3.storage",
"att": [
{
can: "upload/list",
with: "did:key:zAl..1ce",
nb: {
size: 40,
cursor: 'cursor-value-from-previous-invocation',
}
}
],
"prf": [{ "/": "bafy...prf1" }],
"exp": 1685602800,
"s": {
"/": {
"bytes": "7aEDQJbJqmyMxTxcK05XQKWfvxG+Tv+LWCJeE18RSMnciCZ/RQ21U75LA0uFSvIjdqnF5RaauZTE8mh2ZYMBBejdJQ4"
}
}
}
Capability provider MUST issue signed receipt containing UploadListResult
.
type UploadListResult union {
UploadListSuccess "ok"
UploadListFailure "error"
} representation keyed
Capability provider MUST issue UploadListSuccess
result containing page of upload entries for to the space.
type UploadListSuccess struct {
cursor optional string
before optional string
after optional string
size int
results [UploadListItem]
}
type UploadListItem struct {
root &any
shards optional [&ContentArchive]
}
type UploadListFailure struct {
message string
}