Home

Awesome

UCAN Invocation Specification

Version 1.0.0-rc. 1

Editors

Authors

Dependencies

Language

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.

Abstract

UCAN Invocation defines a format for expressing the intention to execute delegated UCAN capabilities, and the attested receipts from an execution.

Introduction

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.

Intuition

Car Keys

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:

Lazy vs Eager Evaluation

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.

Public Resources

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.

Promise Pipelining

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.

Concepts

Roles

Task adds two new roles to UCAN: invoker and executor. The existing UCAN delegator and delegate principals MUST persist to the invocation.

UCAN FieldDelegationInvocation
issDelegator: transfer authority (active)Invoker: request task (active)
audDelegate: gain authority (passive)Executor: perform task (active)

Invoker

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.

Executor

The executor is directed to perform some task described in the UCAN invocation by the invoker.

Life Cycle

At a very high level:

erDiagram
    Delegation }o--|{ Invocation: proves
    Invocation }|--|| Task: requests
    Invocation ||--|| Receipt: returns
    Receipt |o--|{ Task: enqueues

Anatomy

ConceptDescription
CommandFunction application; a description of work to be performed
TaskContextual information for a Command, such as resource limits
InvocationA 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

Type Tag

The UCAN envelope's payload tag MUST be ucan/inv@1.0.0-rc.1.

Invocation Payload

The Invocation Payload attaches sender, receiver, and provenance to the Task.

FieldTypeRequiredDescription
issDIDYesThe DID of the Invoker
subDIDYesThe Subject being invoked
audDIDNoThe DID of the intended Executor if different from the Subject
cmdStringYesThe Command
args{String : Any}YesThe Command's Arguments
prf[&Delegation]YesDelegations that prove the chain of authority
meta{String : Any}NoArbitrary Metadata
nonceBytesNoA unique, random nonce
expInteger | null1YesThe timestamp at which the Invocation becomes invalid
iatInteger1NoThe timestamp at which the Invocation was created
cause&ReceiptNoAn 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:alice@example.com",
    "to": [ "bob@example.com", "carol@example.com" ],
    "subject": "hello",
    "body": "world"
  }
}
// Pseudocode JS Analogy
() => msg.send({
  from: "mailto:alice@example.com",
  to: ["bob@example.com", "carol@example.com"],
  subject: "hello",
  body: "world"
})

Agents

Issuer

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.

Subject

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:

  1. Specifying a particular sub (and thus aud) when [enqueuing new Tasks][enqueue] in a Receipt
  2. Indexing Receipts for reverse lookup and memoization

Audience

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.

Task

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.

Command

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.

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.

Nonce

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).

Proofs

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

Cause

The OPTIONAL cause field is a provenance claim describing which Receipt requested it. This is helpful for tracking chains of Invocations.

Expiration

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.

Issued At

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.

Metadata

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.

Attestation

An Invocation MAY be used to attest to some information. This is in effect a statement to the Issuer (without Audience) that never expires.

Proof Chains

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((&nbsp&nbsp&nbsp&nbspDan&nbsp&nbsp&nbsp&nbsp))
    subject((&nbsp&nbsp&nbsp&nbspAlice&nbsp&nbsp&nbsp&nbsp))

    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

Examples

Interacting with an HTTP API

// 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"}
      ]
    }
    
  }
]

Sending Email

// 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:akiko@example.com",
        "to": [ "boris@example.com", "carol@example.com" ],
        "subject": "Coffee",
        "body": "Let get coffee sometime and talk about UCAN Invocations!"
      },
      "nonce": {"/": {"bytes": "TWFueSBopZ2h0IHdvcs"}},
      "prf": [{"/": "zdpuAzx4sBrBCabrZZqXgvK3NDzh7Mf5mKbG11aBkkMCdLtCp"}],
      "exp": 1697409438
    }
  }
]

Inline WebAssembly

[
  {"/": {"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]
      }
    }
  }
]

Prior Art

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.

7 Acknowledgements

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.

<!-- External Links -->

Footnotes

  1. JavaScript has a single numeric type (Number) for both integers and floats. This representation is defined as a IEEE-754 double-precision floating point number, which has a 53-bit significand. ↩ ↩2