UCAN.storage defines the UCAN protocol used by web3.storage and nft.storage. It inherits all the UCAN characteristics by just defining a specific set of capabilities and facts.
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 RFC 2119.
UCAN.storage describes how our services leverage UCANs to decentralized authorization and access permissions using a specific capability scheme embedded in UCAN tokens.
The rest of this document outlines the storage
scheme, the capabilities it supports and considerations that implementers might want to be aware of when implementing.
Refer to UCAN Spec for general UCAN terminology. This document defines only term specific for UCAN.storage.
A service is an entity that implements this spec, is able to serve as root issuer of UCAN.storage tokens and validate them. Examples of UCAN.storage complaint services are web3.storage and nft.storage.
DIDs in this document use the did:key
method and may be referred to as did:user1
, did:service
for simplicity in examples and descriptions.
The actual representation is always as described in the did:key
specification.
did-key-format := did:key:<mb-value>
mb-value := z[a-km-zA-HJ-NP-Z1-9]+
The JWT header and payload structure in this document serves just a visual reference, the overall container remains the same as in the UCAN spec. For UCAN.storage this document defines just the structure of the att
claims.
The header MUST include all of the following fields:
Field | Type | Description | Required |
---|---|---|---|
alg |
String |
Signature algorithm | Yes |
typ |
String |
Type (MUST be "JWT" ) |
Yes |
ucv |
String |
UCAN Semantic Version (v2.0.0) | Yes |
The payload MUST describe the authorization claims being made, who is involved, and its validity period.
Field | Type | Description | Required |
---|---|---|---|
iss |
String |
Issuer DID (sender) | Yes |
aud |
String |
Audience DID (receiver) | Yes |
nbf |
Number |
Not Before UTC Unix Timestamp (valid from) | No |
exp |
Number |
Expiration UTC Unix Timestamp (valid until) | Yes |
nnc |
String |
Nonce | No |
fct |
Json[] |
Facts (asserted, signed data) | No |
prf |
String[] |
Proof of delegation (witnesses are nested UCANs) | Yes |
att |
Json[] |
Attenuations | Yes |
The REQUIRED att
field contains a set of capabilities as defined in the Capability Scope.
For UCAN.storage att
MUST contain at least one storage
capability.
"att": [
{
"with": "storage://<did_aud>/<did_aud>",
"can": "upload/IMPORT",
}
]
The storage
capability represents the ownership and access to operations over storage resources in a Service.
The resource pointer MUST match the current proof scope. Meaning it will follow the UCAN chain of audiences.
When a Service issues a UCAN for did:user1
the resource will be storage://did:user-1
, if user-1
issues a delegated UCAN to did:user-2
it MAY further restrict the scope of it by setting the resource to storage://did:user-1/did:user-2/
.
When restricting, the issuer can OPTIONALLY add another path segment to the resource URI. Using audience DID will guarantee uniqueness, although it is not REQUIRED to be unique and could be anything i.e. storage://did:user-1/public
.
// Ucan from service to user1
{
"iss": "did:service"
"aud": "did:user-1"
"att": {
"with": "storage://did:user-1"
}
}
// Ucan from user1 to user2
{
"iss": "did:user-1"
"aud": "did:user-2"
"att": {
"with": "storage://did:user-1/did:user-2"
}
}
// Ucan from user2 to service
{
"iss": "did:user2"
"aud": "did:service-did"
"att": {
"with": "storage://did:user-1/did:user-2"
}
}
The upload/*
action allows access to ALL upload operations under the specified resource in the with
field.
"att": [
{
"with": "storage://did:user",
"can": "upload/*",
}
]
The upload/IMPORT
action allows access to importing a CARs under the specified resource in the with
field.
"att": [
{
"with": "storage://did:user",
"can": "upload/IMPORT",
}
]
The upload/IMPORT
action MUST support a OPTIONAL field mh
to constrain an import by multihash.
A Service MAY use this multihash to perform integrity check.
"att": [
{
"with": "storage://did:user",
"can": "upload/IMPORT",
"mh": "CIQJZPAHYP4ZC4SYG2R2UKSYDSRAFEMYVJBAXHMZXQHBGHM7HYWL4RY"
}
]
Marketplace issues delegated restricted tokens to their users.
To limit to single upload session market can issue UCAN for DID for the session with short lifespan. Client app may perform multiple CAR uploads for that specific DID and throw the key once complete.
If UCAN expires user can request another UCAN to extend the session can be issued by marketplace.
This way maketplace can track specific uploads by DIDs.
further can limit by requesting upload specific UCANs and throw them away once upload is done.
- expiry
- upload session DIDs
sequenceDiagram
Note over Service,Marketplace: Service needs to know Marketplace
Marketplace->>Service: Request UCAN for did:market
Service->>Service: Signs UCAN for did:market
Service->>Marketplace: UCAN for did:market <br>with full capabilities
loop Every time UCAN expires
Alice->>Marketplace: Request UCAN for did:alice
Marketplace->>Marketplace: Signs UCAN for did:alice
Marketplace->>Alice: Short lived UCAN for did:alice <br> with upload/IMPORT
end
Alice->>Alice: Signs UCAN for did:service
Alice->>Service: UCAN+Car
// UCAN for did:market
{
"iss": "did:service",
"aud": "did:marketplace",
"exp": 1643905307, // 2 years into the future
"att": {
{
"with": "storage://did:marketplace",
"can": "upload_v1/*"
}
}
}
// UCAN for did:alice
{
"iss": "did:marketplace",
"aud": "did:alice",
"exp": 1643905307, // 15 mins into the future
"att": {
{
"with": "storage://did:marketplace/did:alice",
"can": "upload_v1/IMPORT"
}
}
}
// UCAN for did:service
{
"iss": "did:alice",
"aud": "did:service",
"exp": 1643905306, // =< 15 mins
// call we skip this att since it can't be anything other than the previous ????
"att": {
{
"with": "storage://did:marketplace/did:user", // "with": "prf:*"
"can": "upload_v1/IMPORT" // "can": "ucan/DELEGATE"
},
},
"prf": [
ucan_0, ucan_1
]
}
sequenceDiagram
Note over Service,Marketplace: Service needs to know Marketplace
Marketplace->>Service: Request UCAN for did:market
Service->>Service: Signs UCAN for did:market
Service->>Marketplace: UCAN for did:market <br>with full capabilities
Alice->>Marketplace: Request UCAN for did:alice <br> and a multihash
Marketplace->>Marketplace: Signs UCAN for did:alice
Marketplace->>Alice: UCAN for did:alice with upload/IMPORT <br>constrained to a multihash
Alice->>Alice: Signs UCAN for did:service
Alice->>Service: UCAN+Car
// UCAN for did:market
{
"iss": "did:service",
"aud": "did:marketplace",
"exp": 1643905307, // 2 years into the future
"att": {
{
"with": "storage://did:marketplace",
"can": "upload_v1/*"
}
}
}
// UCAN for did:alice
{
"iss": "did:marketplace",
"aud": "did:alice",
"att": {
{
"with": "storage://did:marketplace/did:user",
"can": "upload_v1/IMPORT",
"mh": "CIQJZPAHYP4ZC4SYG2R2UKSYDSRAFEMYVJBAXHMZXQHBGHM7HYWL4RY"
},
}
}
// UCAN for did:service
{
"iss": "did:user",
"aud": "did:service",
// call we skip this att since it can't be anything other than the previous ????
"att": {
{
"with": "storage://did:marketplace/did:user",
"can": "upload_v1/IMPORT"
},
}
}
protect against replay if nonce is present and ttl is short like 24h+-
https://github.com/ucan-wg/spec#521-invocation-recipient-validation