# UCAN Delegation Specification ## Version 1.0.0 ## Editors [Editors]: #editors - [Brooklyn Zelenka], [Witchcraft Software] ## Authors [Authors]: #authors - [Brooklyn Zelenka], [Witchcraft Software] - [Daniel Holmgren], [Bluesky] - [Irakli Gozalishvili], [Common Tools] - [Philipp Krüger], [number zero] - [Hugo Dias], [Decentralised Experience] ## Dependencies [Dependencies]: #dependencies - [UCAN] ## Language [Language]: #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 [Abstract]: #abstract This specification describes the representation and semantics for delegating attenuated authority between principals. UCAN Delegation provides a cryptographically verifiable container, batched capabilities, hierarchical authority, and a minimal syntactically-driven policy language. # Introduction [Introduction]: #introduction UCAN Delegation is a delegable certificate capability system with runtime-extensibility, ad hoc conditions, cacheability, and focused on ease of use and interoperability. Delegations act as a proofs for [UCAN Invocation]s. Delegation provides a way to "transfer authority without transferring cryptographic keys". As an authorization system, it is more interested in "what can be done" than a list of "who can do what". For more on how Delegation fits into UCAN, please refer to the [high level spec][UCAN]. # [UCAN Envelope] Configuration [UCAN Envelope Configuration]: #ucan-envelope-configuration ## Type Tag [Type Tag]: #type-tag The UCAN envelope tag for UCAN Delegation MUST be set to `ucan/dlg@1.0.0`. ## Delegation Payload [Delegation Payload]: #delegation-payload The Delegation payload MUST describe the authorization claims, who is involved, and its validity period. | Field | Type | Required | Description | |---------|-------------------------------------------|----------|---------------------------------------------------------------------------| | `iss` | `DID` | Yes | Issuer DID (sender). All [DID][did-spec]s are represented as string URLs. | | `aud` | `DID` | Yes | Audience DID (receiver) | | `sub` | `DID \| null` | Yes | Principal that the chain is about (the [Subject]) | | `cmd` | `String` | Yes | The [Command] to eventually invoke | | `pol` | `Policy` | Yes | [Policy] | | `nonce` | `Bytes` | Yes | Nonce | | `meta` | `{String : Any}` | No | [Meta] (asserted, signed data) — is not delegated authority | | `nbf` | `Integer` (53-bits[^js-num-size]) | No | "Not before" UTC Unix Timestamp in seconds (valid from) | | `exp` | `Integer \| null` (53-bits[^js-num-size]) | Yes | Expiration UTC Unix Timestamp in seconds (valid until) | [^js-num-size]: JavaScript has a single numeric type ([`Number`][JS 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. # Capability [Capability]: #capability A capability is the semantically-relevant claim of a delegation. They MUST take the following form: | Field | Type | Required | Description | |--------|---------------|----------|----------------------------------------------------------------------------------------------------------| | `sub` | `DID \| null` | Yes | The [Subject] that this Capability is about | | `cmd` | `Command` | Yes | The [Command] of this Capability | | `pol` | `Policy` | Yes | Additional constraints on eventual Invocation arguments, expressed in the [UCAN Policy Language][Policy] | Here is an illustrative example: ``` js { // ... "sub": "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp" "cmd": "/blog/post/create", "pol": [ ["==", ".status", "draft"], ["all", ".reviewer", ["like", ".email", "*@example.com"]], ["any", ".tags", ["or", ["==", ".", "news"], ["==", ".", "press"]]] ] } ``` ## Subject [Subject]: #subject The Subject MUST be the DID that initiated the delegation chain, or an explicit `null`. Declaring a DID is RECOMMENDED. For more on the `null`, please see the [Powerline] section. ``` js { "sub": "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp", // ... } ``` ### Resource [Resource]: #resource Unlike [Subjects][Subject] and [Commands][Command], Resources are _semantic_ rather than syntactic. The Resource is the "what" that a capability describes. By default, the Resource of a capability is the Subject. This makes the delegation chain self-certifying. ``` js { "sub": "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp", // Subject // ... } ``` In the case where access to an [external resource] is delegated, the Subject MUST own the relationship to the Resource. The Resource SHOULD be referenced by a `uri` key in the relevant [Conditions], except where it would be clearer to do otherwise. This MUST be defined by the Subject and understood by the executor. ``` js { "sub": "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp", "cmd": "/crud/create", "pol": [ ["==", ".url", "https://example.com/blog/"], // Resource managed by the Subject // ... ], // ... } ``` ### Powerline [Powerline]: #powerline > [!WARNING] > Similar to `cmd: "/"` and `pol: []`, this feature (`sub: null`) is very powerful. Use with care. A "Powerline"[^powerbox] is a pattern for automatically delegating _all_ future delegations to another agent regardless of [Subject]. This is achieved by explicitly setting the [Subject] (`sub`) field to `null`. At [Validation] time, the [Subject] MUST be substituted for the directly prior Subject given in the delegation chain. All other fields MUST continue to validate as normal (e.g. [principal alignment][Principal Alignment], [time bounds][Time Bounds], and so on). [^powerbox]: For those familiar with design patterns for object capabilities, a "Powerline" is like a [Powerbox] but adapted for the partition-tolerant, static token context of UCAN. Powerline delegations MUST NOT be used as the root delegation to a resource. A priori there is no such thing as a `null` subject. A very common use case for Powerline is providing a stable DID across multiple agents (e.g. representing a user with multiple devices). This enables the automatic sharing of authority across their devices without needing to share keys or set up a threshold scheme. It is also flexible, since a Powerline delegation MAY be [revoked][Revocation]. ``` mermaid sequenceDiagram autonumber participant Email Server participant Alice Root participant Alice's Phone participant Alice's Tablet participant Alice's Laptop Alice Root ->> Alice's Phone: Delegate {sub: null, cmd: "/"} Alice Root ->> Alice's Tablet: Delegate {sub: null, cmd: "/"} Alice Root ->> Alice's Laptop: Delegate {sub: null, cmd: "/"} Email Server ->> Alice Root: Delegate {sub: "did:example:email", cmd: "/msg/send"} Alice's Tablet -->> Email Server: INVOKE! {sub: "did:example:email", cmd: "/msg/send", proofs: [❹,❷]} ``` Powerline MAY include other restrictions, such as [time bounds][Time Bounds], [Commands][Command], and [Policies][Policy]. For example, the ability to automatically redelegate read-only access to arbitrary CRUD resources could be expressed as: ``` js { "iss": "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp", "aud": "did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme", "sub": null, // 👈 ⚡ Powerline "cmd": "/crud/read", "pol": [], // ... } ``` ## Command [Command]: #command The [Command][High Level Command] MUST be a `/` delimited path describing set of commands delegated. Delegation covers exact [Command] specified and all the commands described by a paths nested under that specified command. > [!NOTE] > The command path syntax is designed to support forward compatible protocol extensions. Backwards-compatible capabilities MAY be introduced as command subpaths. > [!WARNING] > By definition `"/"` implies all of the commands available on a resource, and SHOULD be used with great care. # Policy [Policy]: #policy UCAN Delegation uses predicate logic statements extended with [jq]-inspired selectors as a policy language. Policies are syntactically driven, and MUST constrain the `args` field of an eventual [Invocation]. A Policy is always given as an array of predicates. This top-level array is implicitly treated as a logical `and`, where `args` MUST pass validation of every top-level predicate. Policies are structured as trees. With the exception of subtrees under `any`, `or`, and `not`, every leaf MUST evaluate to `true`. A Policy is an array of statements. Every statement MUST take the form `[operator, selector, argument]` except for connectives (`and`, `or`, `not`) which MUST take the form `[operator, argument]`. ```ipldsch -- Statements type Statement union { | Equality | Like | Inequality | Connective | Negation | Quantifier } -- Equality type EqOp enum { | Eq ("==") | Neq ("!=") } type Equality struct { op EqOp sel Selector val Any } representation tuple type LikeOp enum { | Like ("like") } type Like struct { op LikeOp sel Selector str Wildcard } representation tuple -- Inequality type IneqOp enum { | GT (">") | GTE (">=") | LT ("<") | LTE ("<=") } type Inequality struct { op IneqOp sel Selector val Number } representation tuple -- Connectives type NegateOp { | Not ("not") } type Negation struct { op NegateOp smt Statement } representation tuple type ConnectiveOp enum { | And ("and") | Or ("or") } type Connective struct { op ConnectiveOp smts [Statement] } representation tuple -- Quantification type QuantifierOp enum { | All ("all") | Any ("any") } type Quantifier struct { op QuantiefierOp sel Selector smt Statement } representation tuple -- Primitives type Selector = string type Number union { | NumInt int | NumFloat float } representation kinded type Wildcard = string ``` ## Comparisons [Comparisons]: #comparisons | Operator | Arguments | Example | |----------|--------------------------------|----------------------------------| | `==` | `Selector, IPLD` | `["==", ".a", [1, 2, {"b": 3}]]` | | `!=` | `Selector, IPLD` | `["!=", ".a", [1, 2, {"b": 3}]]` | | `<` | `Selector, (integer \| float)` | `["<", ".a", 1]` | | `<=` | `Selector, (integer \| float)` | `["<=", ".a", 1]` | | `>` | `Selector, (integer \| float)` | `[">", ".a", 1]` | | `>=` | `Selector, (integer \| float)` | `[">=", ".a", 1]` | Literal equality (`==`) MUST match the resolved selector to entire IPLD argument. This is a "deep comparison". Literal inequality (`!=`) is equivalent to `["not", ["==", selector, value]]`. Numeric inequalities MUST be agnostic to numeric type. In other words, the decimal representation is considered equivalent to an integer (`1 == 1.0 == 1.00`). Attempting to compare a non-numeric type MUST return false and MUST NOT throw an exception. ## Glob Matching [Glob Matching]: #glob-matching | Operator | Arguments | Example | |----------|---------------------|---------------------------------------| | `like` | `Selector, Pattern` | `["like", ".email", "*@example.com"]` | Glob patterns MUST only include one special character: `*` ("wildcard"). There is no single character matcher. As many `*`s as desired MAY be used. Non-wildcard `*`-literals MUST be escaped (`"\*"`). Attempting to match on a non-string MUST return false and MUST NOT throw an exception. The wildcard represents zero-or-more characters. The following string literals MUST pass validation for the pattern `"Alice\*, Bob*, Carol.`: * `"Alice*, Bob, Carol."` * `"Alice*, Bob, Dan, Erin, Carol."` * `"Alice*, Bob , Carol."` * `"Alice*, Bob*, Carol."` The following MUST NOT pass validation for that same pattern: * `"Alice*, Bob, Carol"` (missing the final `.`) * `"Alice*, Bob*, Carol!"` (final `.` MUST NOT be treated as a wildcard) * `"Alice, Bob, Carol."` (missing the `*` after `Alice`) * `"Alice Cooper, Bob, Carol."` (the `*` after `Alice` is an escaped literal in the pattern) * `" Alice*, Bob, Carol. "` (whitespace in the pattern is significant) ## Connectives [Connectives]: #connectives Connectives add context to their enclosed statement(s). | Operator | Argument | Example | |----------|---------------|--------------------------------------------| | `and` | `[Statement]` | `["and", [[">", ".a", 1], [">", ".b", 2]]` | | `or` | `[Statement]` | `["or", [[">", ".a", 1], [">", ".b", 2]]` | | `not` | `Statement` | `["not", [">", ".a", 1]]` | ### And [And]: #and `and` MUST take an arbitrarily long array of statements, and require that every inner statement be true. An empty array MUST be treated as true. ```js // Data { name: "Katie", age: 35, nationalities: ["Canadian", "South African"] } ["and", []] // ⬆️ true ["and", [ ["==", ".name", "Katie"], [">=", ".age", 21] ]] // ⬆️ true ["and", [ ["==", ".name", "Katie"], [">=", ".age", 21], ["==", ".nationalities", ["American"]] // ️⬅️ false ]] // ⬆️ false ``` ### Or [Or]: #or `or` MUST take an arbitrarily long array of statements, and require that at least one inner statement be true. An empty array MUST be treated as true. ```js // Data { name: "Katie", age: 35, nationalities: ["Canadian", "South African"] } ["or", []] // ⬆️ true ["or", [ ["==", ".name", "Katie"], // ⬅️ true [">", ".age", 45] ]] // ⬆️ true ``` ### Not [Not]: #not `not` MUST invert the truth value of the inner statement. For example, if `["==", ".a", 1]` were false (`.a` is not 1), then `["not", ["==", ".a", 1]]` would be true. ```js // Data { name: "Katie", nationalities: ["Canadian", "South African"] } ["not", ["and", [ ["==", ".name", "Katie"], ["==", ".nationalities", ["American"]] // ⬅️ false ]]] // ⬆️ true ``` ## Quantification [Quantification]: #quantification When a selector resolves to a collection (an array or map), quantifiers provide a way to extend `and` and `or` to their contents. Attempting to quantify over a non-collection MUST return false and MUST NOT throw an exception. Quantifying over an array is straightforward: it MUST apply the inner statement to each array value. Quantifying over a map MUST extract the values (discarding the keys), and then MUST proceed on the values the same as if it were an array. | Operator | Argument(s) | Example | |----------|-------------------------|---------------------------------| | `all` | `Selector, [Statement]` | `["all", ".a" [">", ".b", 1]]` | | `any` | `Selector, [Statement]` | `["any", ".a" [">", ".b", 1]]` | `all` extends `and` over collections. `any` extends `or` over collections. For example: ``` js const args = {"a": [{"b": 1}, {"b": 2}, {"z": [7, 8, 9]}]} const statement = ["all", ".a", [">", ".b", 0]] // Outer Selector Substitution ["all", [{"b": 1}, {"b": 2}, {"z": [7, 8, 9]}], [">", ".b", 0]] // Predicate Reduction ["and", [ [">", 1, 0], [">", 2, 0], [">", null, 0] ]] ["and", [ true, true, false // ⬅️ ]] false // ❌ ``` ``` js const args = {"a": [{"b": 1}, {"b": 2}, {"z": [7, 8, 9]}]} const statement = ["any", ".a", ["==", ".b", 2]] // Reduction ["any", [{"b": 1}, {"b": 2}, {"z": [7, 8, 9]}], ["==", ".b", 2]] ["or", [ ["==", 1, 2], ["==", 2, 2], ["==", null, 2] ]] ["or", [ false, true, // ⬅️ false ]] true // ✅ ``` ### Nested Quantification [Nested Quantification]: #nested-quantification Quantified statements MAY be nested. For example, the below states that someone with the email `fraud@example.com` is required to be among the receipts of every newsletter. ``` js ["all", ".newsletters", ["any", ".recipients", ["==", ".email", "fraud@example.com"]]] ``` ## Selectors [Selectors]: #selectors Selector syntax is closely based on [jq]'s "filters". They operate on an [Invocation]'s `args` object. Selectors MUST only include the following features: | Selector Name | Examples | Notes | |------------------------|-------------------------------------|------------------------------------------------------------------------------------------------------------------------| | Identity | `.` | Take the entire argument | | Dotted field name | `.foo`, `.bar0_` | Shorthand for selecting in a map by key (with exceptions, see below) | | Unambiguous field name | `["."]`, `["$_*"], ["1"]` | Select in a map by arbitrary key | | Collection values | `[]` | Expands out all of the children that match the remaining path. On lists this is a noop. On maps, this extracts values. | | List index | `[0]`, `[42]` | The list element of a list by 0-index. | | Negative list index | `[-1]`, `[-42]` | The list element by index from the end. `-1` is the index for the last element. | | List slices | `[7:11]`, `[2:]`, `[:42]`, `[0:-2]` | The range of elements by their indices. | | Optional | `.foo?`, `["nope"]?` | Returns `null` on what would otherwise fail | Every selection MUST begin and/or end with a single dot. Multiple dots (e.g. `..`, `...`) MUST NOT be used anywhere in a selector. The optional operator is idempotent, and repeated optionals (`.foo???`) MUST be treated as a single one. For example, consider the following `args` from an `Invocation`: ``` json { "args": { "from": "alice@example.com", "to": ["bob@example.com", "carol@not.example.com", "dan@example.com"], "cc": ["fraud@example.com"], "title": "Meeting Confirmation", "body": "I'll see you on Tuesday" } } ```
| Selector | Returned Value |
"." |
```json { "from": "alice@example.com", "to": ["bob@example.com", "carol@not.example.com", "dan@example.com"], "cc": ["fraud@example.com"], "title": "Meeting Confirmation", "body": "I'll see you on Tuesday" } ``` |
".title" |
``` json "Meeting Confirmation" ``` |
".cc" |
``` json ["fraud@example.com"] ``` |
".to[1]" |
``` json "carol@not.example.com" ``` |
".to[-1]" |
``` json "dan@example.com" ``` |
".to[99]?" |
``` json null ``` |
".to[99]" |
fail to resolve |