When syndicate-server starts, it creates a gatekeeper service entity, which accepts resolve assertions requesting conversion of a long-lived credential to a live reference. The gatekeeper is the default object, available as OID 0 to peers at the other end of relay listener connections.

Gatekeeper protocol

Resolve = <resolve @step Step @observer #:Resolved> .
Resolved = <accepted @responderSession #:any> / Rejected .
Step = <<rec> @stepType symbol [@detail any]> .
Rejected = <rejected @detail any> .

When a request to resolve a given credential, a Step, appears, the gatekeeper entity queries a dataspace (by default, the server's top-level $config dataspace) for bind assertions:

Bind = <bind @description Description @target #:any @observer BindObserver> .
Description = <<rec> @stepType symbol [@detail any]> .
BindObserver = @present #:Bound / @absent #f .
Bound = <bound @pathStep PathStep> / Rejected .

A bind assertion specifies the reference that backs a long-lived credential, and gives instructions for checking the validity of a presented credential. Each bind assertion matching a requested Step is checked using the stepType-specific detail in the Description combined with the detail from the Step. If the checks pass, the target entity from the bind is asserted in an accepted record to the observer in the resolve. If the checks fail, a rejected record is asserted to the observer. If no bind matching a particular Step exists, the system just waits; this allows it to be relaxed about ordering of events.

However, besides waiting for a bind, the gatekeeper asserts a resolve of its own into its associated dataspace, with the same Step that it received but a different observer. If, before an appropriate bind appears, a Resolved assertion is sent to this resolve's observer, the gatekeeper stops waiting for a bind and relays the response on to the ultimate requester directly. This way, entities can keep an eye out for resolve requests that will never complete, and answer rejected to them even when no matching bind exists. Entities can also use resolve requests to synthesize a bind in a "just-in-time" fashion.


A "sturdyref" is a long-lived certificate including a cryptographic signature that can be upgraded by a gatekeeper entity to a live reference to the entity named in the sturdyref. The current sturdyref implementation is based on the design of Macaroons.

Example. The sturdyref <ref {oid: "syndicate" sig: #[acowDB2/oI+6aSEC3YIxGg==]}> is valid for the associated Bind assertion <bind <ref {oid: "syndicate" key: #[]}> $ds #f>.

The following definitions are taken from the sturdy.prs schema. For further detail, see the reference.

SturdyStepType = =ref .
SturdyStepDetail = Parameters .
SturdyRef = <ref @parameters Parameters> .

First, when used as a Step, a sturdyref uses ref as its stepType and Parameters as its detail. A sturdyref as a whole, then, is just the combination of the type and parameters in a record.

Parameters = {
  oid: any,
  sig: bytes,
} & @caveats CaveatsField .

CaveatsField =
  / @present { caveats: [Caveat ...] }
  / @invalid { caveats: any }
  / @absent {} .

The Parameters of a sturdyref are the oid field, which is a free-form value that the targeted service chooses to name itself, and the sig, which is an iterated keyed-HMAC construction, just as in macaroons. The sig is derived from the oid and the service's secret key:

SturdyDescriptionDetail = {
  oid: any,
  key: bytes,
} .

In a Bind with stepType of ref, the detail in the Description should be a SturdyDescriptionDetail value. The key is the secret key used to compute sigs on sturdyrefs; the oid connects references with their defining Binds.

To compute a sig for a sturdyref, the service's secret key is first used to key an HMAC of the oid. Then, the result is used to key an HMAC of the (canonical form of the) first Caveat in the ref's caveats, if any. Each Caveat's HMAC becomes the key for the next in the caveatChain. The sig is the final result.

When validating sturdyrefs, compute the sig fresh, starting from the key and oid, and compare the final result to the presented sig.

Attenuation of authority

When it comes to publishing assertions or sending messages to the entity denoted by a sturdyref, the caveatChain is used to attenuate the authority denoted by the sturdyref by filtering and/or rewriting assertion and message bodies. The caveatChain is run right to left, with newer rewrites-and-filters at the right-hand end of the chain and older ones at the left-hand end. Of course, an empty caveatChain is an unattenuated reference. The structure and interpretation of Caveats is described fully in the relevant section of the Syndicate network protocol specification.

The term "caveat" is shamelessly taken from macaroons, though our caveats presently embody only what in the Macaroons paper are called "first-party caveats" over assertion structure; future versions of the server may add "third-party caveats" and other, richer, predicates over assertions.