- Brooklyn Zelenka, Witchcraft Software
- Irakli Gozalishvili, Protocol Labs
- Zeeshan Lakhani, Oxide Computer
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 when, and only when, they appear in all capitals, as shown here.
UCAN Invocation defines a format for expressing the intention to execute delegated UCAN capabilities, and the attested receipts from an execution.
Just because you can doesn't mean that you should
— Anonymous
When authorization is communicated without such context, it's like receiving a key in the mail with no hint about what to do with it [...] After an object receives this message, she can invoke arg if she chooses, but why would she ever choose to do so?
Mark Miller, [E-lang Mailing List, 2000 Oct 18]
UCAN is a chained-capability format. A UCAN contains all of the information that one would need to perform some task, and the provable authority to do so. This begs the question: can UCAN be used directly as an RPC language?
Some teams have had success with UCAN directly for RPC when the intention is clear from context. This can be successful when there is more information on the channel than the UCAN itself (such as an HTTP path that a UCAN is sent to). However, capability invocation contains strictly more information than delegation: all of the authority of UCAN, plus the command to perform the task.
Consider the following fictitious scenario:
Akiko is going away for the weekend. Her good friend Boris is going to borrow her car while she's away. They meet at a nearby cafe, and Akiko hands Boris her car keys. Boris now has the capability to drive Akiko's car whenever he wants to. Depending on their plans for the rest of the day, Akiko may find Boris quite rude if he immediately leaves the cafe to go for a drive. On the other hand, if Akiko asks Boris to run some last minute pre-vacation errands for that require a car, she may expect Boris to immediately drive off.
To put this in terms closer to a UCAN flow:
sequenceDiagram
participant 🚗
actor Akiko
actor Boris
autonumber
Note over 🚗, Akiko: Akiko buys a car
🚗 -->> Akiko: Delegate(Drive 🚗)
Note over Akiko, Boris: Boris offers to run errands for Akiko
Boris -->> Akiko: Delegate(Boris to run errands)
Note over Akiko, Boris: Akiko gives Boris access to her car
Akiko -->> Boris: Delegate(Drive 🚗)
Note over 🚗, Boris: Akiko asks Boris to use her car to run errands
Akiko ->> Boris: Invoke!(Boris to run errands, using 🚗 (➌))
Boris ->> 🚗: Invoke!(Drive 🚗)
In the example above, steps ➌ and ➍ are qualitatively different:
- Step ➌ grants authority (to drive the car)
- Step ➍ is a command to do so
In a referentially transparent setting, the description of a task is equivalent to having done so: a function and its results are interchangeable. Programming languages with call-by-need semantics have shown that this can be an elegant programming model, especially for pure functions. However, when something will run can sometimes be unclear.
Most languages use eager evaluation. Eager languages must contend directly with the distinction between a reference to a function and a command to run it. For instance, in JavaScript, adding parentheses to a function will run it. Omitting them lets the program pass around a reference to the function without immediately invoking it.
const message = () => alert("hello world")
message // Nothing happens
message() // A message interrupts the user
Delegating a capability is like the statement message
. Task is akin to message()
. It's true that sometimes we know to run things from their surrounding context without the parentheses:
[1, 2, 3].map(message) // Message runs 3 times
However, there is clearly a distinction between passing a function and invoking it. The same is true for capabilities: delegating the authority to do something is not the same as asking for it to be done immediately, even if sometimes it's clear from context.
A core part of UCAN's design is interacting with the wider, non-UCAN world. Many resources are open to anyone to access, such as unauthenticated web endpoints. Unlike UCAN-controlled resources, an invocation on public resources is both possible, and a hard requirement for initiating a flow (e.g. sign up). These cases typically involve a reference passed out of band (such as a web link). Due to designation with authorization, knowing the URI of a public resource is often sufficient for interacting with it. In these cases, the Executor MAY accept Invocations without having a "closed-loop" proof chain, but this SHOULD NOT be the default behavior.
UCAN Promise extends UCAN Invocation with distributed promise pipelines. Promises are helpful in a wide variety of situations for efficiency and convenience. Implementations supporting UCAN Promises is RECOMMENDED.
Task adds two new roles to UCAN: invoker and executor. The existing UCAN delegator and delegate principals MUST persist to the invocation.
UCAN Field | Delegation | Invocation |
---|---|---|
iss |
Delegator: transfer authority (active) | Invoker: request task (active) |
aud |
Delegate: gain authority (passive) | Executor: perform task (active) |
The invoker signals to the executor that a task associated with a UCAN SHOULD be performed.
The invoker MUST be the UCAN delegator. Their DID MUST be authenticated in the iss
field of the contained UCAN.
The executor is directed to perform some task described in the UCAN invocation by the invoker.
At a very high level:
- A Task abstractly describes some Action to be run
- An Invocation attaches proven (delegated) authority to a Task, and requests it be run by a certain Agent
- A Receipt MAY request that the Invoker enqueue more Tasks
erDiagram
Delegation }o--|{ Invocation: proves
Invocation }|--|| Task: requests
Invocation ||--|| Receipt: returns
Receipt |o--|{ Task: enqueues
Concept | Description |
---|---|
Command | Function application; a description of work to be performed |
Task | Contextual information for a Command, such as resource limits |
Invocation | A request to perform some Task based on delegated authority |
A request for some work to be done (or to "exercise your authority") is an Invocation.
flowchart TD
subgraph Invocation
SignatureBytes["Signature (raw bytes)"]
subgraph SigPayload ["Signature Payload"]
VarsigHeader["Varsig Header"]
subgraph InvocationPayload ["Invocation Payload"]
iss
sub
do
args
prf
cause["cause (optional)"]
etc["..."]
end
end
end
cause -.->|CID| Receipt
As noted in the introduction, there is a difference between a reference to a function and calling that function. The Invocation is a request to the Executor to perform the enclosed Task. Invocation Payloads are not executable until they have been signed and Delegation proofs validated.
Note that the Invocation MUST include the Signature envelope. An Invocation Payload on its own MUST NOT be considered a valid Invocation.
UCAN Envelope Configuration
The UCAN envelope's payload tag MUST be ucan/[email protected]
.
The Invocation Payload attaches sender, receiver, and provenance to the Task.
Field | Type | Required | Description |
---|---|---|---|
iss |
DID |
Yes | The DID of the Invoker |
sub |
DID |
Yes | The Subject being invoked |
aud |
DID |
No | The DID of the intended Executor if different from the Subject |
cmd |
String |
Yes | The Command |
args |
{String : Any} |
Yes | The Command's Arguments |
prf |
[&Delegation] |
Yes | Delegations that prove the chain of authority |
meta |
{String : Any} |
No | Arbitrary Metadata |
nonce |
Bytes |
No | A unique, random nonce |
exp |
Integer | null 1 |
Yes | The timestamp at which the Invocation becomes invalid |
iat |
Integer 1 |
No | The timestamp at which the Invocation was created |
cause |
&Receipt |
No | An OPTIONAL CID of the Receipt that enqueued the Task |
The shape of the args
MUST be defined by the cmd
field type. This is similar to how a method or message contain certain data shapes in object oriented or actor model languages respectively. Using the JavaScript analogy from the introduction, an Action is similar to wrapping a call in a closure:
// Command
{
"cmd": "/msg/send",
"args": {
"from": "mailto:[email protected]",
"to": [ "[email protected]", "[email protected]" ],
"subject": "hello",
"body": "world"
}
}
// Pseudocode JS Analogy
() => msg.send({
from: "mailto:[email protected]",
to: ["[email protected]", "[email protected]"],
subject: "hello",
body: "world"
})
The iss
field MUST include the Issuer of the Invocation. This DID URL MUST dereference to the public key which proves the signature over the payload included in the envelope.
The REQUIRED sub
field both parameterizes over a specific agent, and acts as a namespace for how to interpret the Command. This is especially critical for two parts of the life cycle:
- Specifying a particular
sub
(and thusaud
) when [enqueuing new Tasks][enqueue] in a Receipt - Indexing Receipts for reverse lookup and memoization
The OPTIONAL aud
field specified the intended recipient of Invocation, otherwise the Audience MUST be assumed to the Subject. This is useful for message routing, command brokers, proxy execution, gateways, replicated state machines, and so on.
A Task is the subset of Invocation fields that uniquely determine the work to be performed. The nonce is important for distinguishing between non-idempotent executions of a Task by making the group together unique.
A Task MUST be uniquely defined by a Task ID that is the CID of the following fields as a keyed map:
Tasks that describe pure functions — or other strategies like fan-out racing — SHOULD have the same Task ID by using the same nonce.
The REQUIRED Command (cmd
) field MUST contain a concrete, dispatchable message that can be sent to the Executor. The Command MUST define the shape of the data in the Arguments.
The REQUIRED Arguments (args
) field, MAY contain any parameters expected by the Command. The Subject MUST be considered the authority on the shape of this data. This field MUST be representable as a map or keyword list.
The Arguments MUST pass validation of the Policies on all of the UCAN Delegations in the Proofs field. If any Policy reports failure against the Invocation's Arguments, the Invocation MUST be rejected.
The REQUIRED nonce
field MUST include a random nonce. This field ensures that multiple (non-idempotent) invocations are unique. The nonce SHOULD be empty (0x
) for Commands that are idempotent (such as deterministic Wasm modules or standards-abiding HTTP PUT requests).
The prf
field lists the path of authority from the Subject to the Invoker. This MUST be an array of CIDs pointing Delegations starting from the root Delegation (issued by the Subject), in strict sequence where the aud
of the previous Delegation matches the iss
of the next Delegation.
See Proof Chains for more detail
The OPTIONAL cause
field is a provenance claim describing which Receipt requested it. This is helpful for tracking chains of Invocations.
The REQUIRED nullable field exp
defines when the Invocation SHOULD time out. Setting a timeout within a a few minutes is RECOMMENDED as it accounts for clock skew but limits the ability of an attacker to take advantage of an intercepted Invocation. In general, the smaller the time window the better. This is both expressive (defines a timeout, which is a best practice), and prevents replays.
The OPTIONAL iat
field MAY contain an issuance timestamp. This time SHOULD NOT be trusted; it is only a claim by the Invoker of their system time. System clocks often have clock skew, or a Byzantine Invoker could claim an arbitrary time.
The OPTIONAL meta
field MAY include arbitrary metadata or extensible fields. For example, Wasm fuel, an internal job ID, references to GitHub Issues, and so on. This data MAY be used by the Executor.
An Invocation MAY be used to attest to some information. This is in effect a statement to the Issuer (without Audience) that never expires.
A Task MUST include the entire UCAN Delegation proof chain in the prf
field. The chain MUST form a direct line of authority, starting with the delegation with an aud
that matches the Invoker, and ending with a delegation where the iss
matches the sub
. The sub
throughout MUST match the aud
of the Invocation.
flowchart RL
invoker((    Dan    ))
subject((    Alice    ))
subject -- controls --> resource[(Storage)]
rootCap -- references --> resource
subgraph Delegations
subgraph root [Root UCAN]
subgraph rooting [Root Issuer]
rootIss(iss: Alice)
rootSub(sub: Alice)
end
rootCap("(Storage, crud/*)")
rootAud(aud: Bob)
end
subgraph del1 [Delegated UCAN]
del1Iss(iss: Bob) --> rootAud
del1Sub(sub: Alice)
del1Aud(aud: Carol)
del1Cap("(Storage, crud/*)") --> rootCap
del1Sub --> rootSub
end
subgraph del2 [Delegated UCAN]
del2Iss(iss: Carol) --> del1Aud
del2Sub(sub: Alice)
del2Aud(aud: Dan)
del2Cap("(Storage, crud/*)") --> del1Cap
del2Sub --> del1Sub
end
end
subgraph inv [Invocation]
invIss(iss: Dan)
args("args: [Storage, crud/update, (key, value)]")
invSub(aud: Alice)
prf("proofs")
end
invIss --> del2Aud
invoker --> invIss
args --> del2Cap
invSub --> del2Sub
rootIss --> subject
rootSub --> subject
prf --> Delegations
// DAG-JSON
[
{"/": {"bytes": "7aEDQIscUKVuAIB2Yj6jdX5ru9OcnQLxLutvHPjeMD3pbtHIoErFpo7OoC79Oe2ShgQMLbo2e6dvHh9scqHKEOmieA0"}},
{
"h": {"/": {"bytes": "NBIFEgEAcQ"}},
"ucan/i/1.0.0-rc.1": {
"iss": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
"sub": "did:key:z6MkrZ1r5XBFZjBU34qyD8fueMbMRkKw17BZaq2ivKFjnz2z",
"cmd": "/crud/create",
"args": {
"uri": "https://example.com/blog/posts",
"headers": {
"content-type": "application/json"
},
"payload": {
"title": "UCAN for Fun an Profit",
"body": "UCAN is great!",
"topics": ["authz", "journal"],
"draft": true
}
},
"nonce": {"/": {"bytes": "TWFueSBopvcs"}},
"meta": {
"env": "development",
"tags": ["blog", "post", "pr#123"]
},
"exp": 1697409438
"prf": [
{"/": "zdpuAzx4sBrBCabrZZqXgvK3NDzh7Mf5mKbG11aBkkMCdLtCp"},
{"/": "zdpuApTCXfoKh2sB1KaUaVSGofCBNPUnXoBb6WiCeitXEibZy"},
{"/": "zdpuAoFdXRPw4n6TLcncoDhq1Mr6FGbpjAiEtqSBrTSaYMKkf"}
]
}
}
]
// DAG-JSON
[
{"/": {"bytes": "7aEDQIscUKVuAIB2Yj6jdX5ru9OcnQLxLutvHPjeMD3pbtHIoErFpo7OoC79Oe2ShgQMLbo2e6dvHh9scqHKEOmieA0"}},
{
"h": {"/": {"bytes": "NBIFEgEAcQ"}},
"ucan/i/1.0.0-rc.1": {
"iss": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
"aud": "did:key:z6MkrZ1r5XBFZjBU34qyD8fueMbMRkKw17BZaq2ivKFjnz2z",
"sub": "did:key:z6MkrZ1r5XBFZjBU34qyD8fueMbMRkKw17BZaq2ivKFjnz2z",
"cmd": "/msg/send",
"args": {
"from": "mailto:[email protected]",
"to": [ "[email protected]", "[email protected]" ],
"subject": "Coffee",
"body": "Let get coffee sometime and talk about UCAN Invocations!"
},
"nonce": {"/": {"bytes": "TWFueSBopZ2h0IHdvcs"}},
"prf": [{"/": "zdpuAzx4sBrBCabrZZqXgvK3NDzh7Mf5mKbG11aBkkMCdLtCp"}],
"exp": 1697409438
}
}
]
[
{"/": {"bytes": "7aEDQIscUKVuAIB2Yj6jdX5ru9OcnQLxLutvHPjeMD3pbtHIoErFpo7OoC79Oe2ShgQMLbo2e6dvHh9scqHKEOmieA0"}},
{
"h": {"/": {"bytes": "NBIFEgEAcQ"}},
"ucan/i/1.0.0-rc.1": {
"iss": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
"aud": "did:key:z6MkrZ1r5XBFZjBU34qyD8fueMbMRkKw17BZaq2ivKFjnz2z",
"sub": "did:key:z6MkrZ1r5XBFZjBU34qyD8fueMbMRkKw17BZaq2ivKFjnz2z",
"meta": {"fuel": 999999},
"nonce": {"/": {"bytes": ""}}, // NOTE: as stated above, idempotent Actions should always have the same nonce
"cmd": "/wasm/run",
"args": {
"mod": "data:application/wasm;base64,AHdhc21lci11bml2ZXJzYWwAAAAAAOAEAAAAAAAAAAD9e7+p/QMAkSAEABH9e8GowANf1uz///8UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////8AAAAACAAAACoAAAAIAAAABAAAACsAAAAMAAAACAAAANz///8AAAAA1P///wMAAAAlAAAALAAAAAAAAAAUAAAA/Xu/qf0DAJHzDx/44wMBqvMDAqphAkC5YAA/1mACALnzB0H4/XvBqMADX9bU////LAAAAAAAAAAAAAAAAAAAAAAAAAAvVXNlcnMvZXhwZWRlL0Rlc2t0b3AvdGVzdC53YXQAAGFkZF9vbmUHAAAAAAAAAAAAAAAAYWRkX29uZV9mAAAADAAAAAAAAAABAAAAAAAAAAkAAADk////AAAAAPz///8BAAAA9f///wEAAAAAAAAAAQAAAB4AAACM////pP///wAAAACc////AQAAAAAAAAAAAAAAnP///wAAAAAAAAAAlP7//wAAAACM/v//iP///wAAAAABAAAAiP///6D///8BAAAAqP///wEAAACk////AAAAAJz///8AAAAAlP///wAAAACM////AAAAAIT///8AAAAAAAAAAAAAAAAAAAAAAAAAAET+//8BAAAAWP7//wEAAABY/v//AQAAAID+//8BAAAAxP7//wEAAADU/v//AAAAAMz+//8AAAAAxP7//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU////pP///wAAAAAAAQEBAQAAAAAAAACQ////AAAAAIj///8AAAAAAAAAAAAAAADQAQAAAAAAAA==",
"fun": "add_one",
"params": [42]
}
}
}
]
ucanto RPC from DAG House is a production system that uses UCAN as the basis for an RPC layer.
The Capability Transport Protocol (CapTP) is one of the most influential object-capability systems, and forms the basis for much of the rest of the items on this list.
The Object Capability Network (OCapN) protocol extends CapTP with a generalized networking layer. It has implementations from the Spritely Institute and Agoric. At time of writing, it is in the process of being standardized.
Electronic Rights Transfer Protocol (ERTP) builds on top of CapTP concepts for blockchain & digital asset use cases.
Cap 'n Proto RPC is an influential RPC framework based on concepts from CapTP.
Many thanks to Mark Miller for his trail blazing work on capability systems.
Many thanks to Luke Marsen and Simon Worthington for their feedback on invocation model from their work on Bacalhau and IPVM.
Thanks to Marc-Antoine Parent for his discussions of the distinction between declarations and directives both in and out of a UCAN context.
Many thanks to Quinn Wilton for her discussion of speech acts, the dangers of signing canonicalized data, and ergonomics.
Thanks to Blaine Cook for sharing their experiences with OAuth 1, irreversible design decisions, and advocating for keeping the spec simple-but-evolvable.
Thanks to Philipp Krüger for the enthusiastic feedback on the overall design and encoding.
Thanks to Christine Lemmer-Webber for the many conversations about capability systems and the programming models that they enable.
Thanks to Rod Vagg for the clarifications on IPLD Schema implicits and the general IPLD worldview
Many thanks to Juan Caballero for his detailed questions and comments to help polish the spec.