Decentralized authorization systems face a fundamental challenge: once a capability is issued, it remains valid indefinitely unless explicitly revoked. In networks where revocation information cannot be reliably propagated — due to partitions, offline operation, or lack of centralized infrastructure — this creates the "zombie capability" problem. Existing solutions either centralize revocation (defeating decentralization) or lack temporal freshness guarantees altogether.

Lease-CAP introduces Liveness-Bound Capabilities (LBC) — a novel temporal authorization layer where capabilities decay unless actively maintained through periodic synchronization. Unlike traditional capabilities that remain valid until explicit revocation, LBC requires controllers to regularly prove continued authorization to issuers. This creates a provable liveness guarantee for decentralized authorization, solving the zombie capability problem without central revocation services.

The specification introduces a STALE state — a bounded grace period for renewal that acknowledges network reality while maintaining security boundaries. During this window, verifiers require controllers to synchronize with the issuer before granting access, but do not immediately deny service. This elegant compromise enables seamless operation during temporary network partitions without compromising security.

A key architectural insight is the strict separation between the static capability credential (which describes what authority is granted) and the dynamic lease state (which captures when the capability was last synchronized). The immutable Verifiable Credential never contains timestamps; instead, an issuer-signed LeaseSyncResponse carries the current lastSync timestamp. Verifiers derive effective lease state exclusively from the latest valid response, never from fields embedded in the credential itself. This separation prevents forgery, supports multi-device scenarios, and enables efficient state management.

Lease-CAP is designed as a temporal control layer applicable to any capability-based authorization system, including UCAN, ZCAP-LD, and DIDComm-based agent-to-agent communication. The specification provides complete protocol definitions, cryptographic proof formats, verification algorithms, security analysis, and production-ready reference implementations in TypeScript and Python.

This document is an Editor's Draft being prepared for consideration by the W3C Credentials Community Group (CCG). It is not a W3C Standard nor is it on the W3C Standards Track.

Implementation Status: The specification has reached production readiness with complete reference implementations:

Draft Note: This draft incorporates feedback from implementers and security reviewers. Key areas under active discussion include offline mode parameters, delegation chain depth limits, and multi-device synchronization semantics. Implementers are encouraged to provide feedback through the GitHub issue tracker.

This document is governed by the W3C Patent Policy.

Introduction

Authorization in decentralized systems presents unique challenges absent in traditional client-server architectures. When there is no central authority to consult for every access decision, systems must rely on cryptographic proofs of authorization — capabilities that can be presented to any verifier without real-time issuer involvement. This delegation of authority is powerful but introduces fundamental security questions: How does a capability ever become invalid? How does an issuer revoke access? How does a verifier know that the controller still deserves access?

Problem Statement

Traditional capability systems suffer from what we term the "zombie capability" problem: once issued, capabilities remain valid indefinitely unless explicitly revoked. In centralized systems, revocation can be enforced through token introspection or revocation lists. However, in decentralized systems where revocation information cannot be reliably propagated — due to network partitions, offline operation, or the absence of a centralized revocation authority — this creates significant security risks.

Consider a typical scenario: An employee leaves an organization but possesses a capability issued months ago for accessing cloud resources. Without a central revocation service that the employee cannot bypass, that capability may remain usable indefinitely. Existing decentralized capability systems offer limited solutions:

The core insight of Lease-CAP is that authorization without liveness is incomplete in decentralized systems. A capability should not be a static, unchanging assertion of authority; rather, it should represent a continuing relationship that requires active maintenance. This shift from passive to active authorization fundamentally changes the security properties of decentralized systems.

Solution Overview

Lease-CAP introduces Liveness-Bound Capabilities (LBC) where validity is a function of continuous participation, not just issuance. Each capability carries a lease specification that defines two key parameters:

Capabilities transition through four deterministic states as time advances:

State Condition Verifier Action Controller Action Security Property
ACTIVE N ≤ L + T + ε Grant access normally Proactive sync recommended before TTL expiration Full authorization; no issuer contact required
STALE L + T + ε < N ≤ L + T + G + ε Return 403 with sync endpoint; require sync Immediate sync required before retry Bounded grace period; liveness preserved during partitions
EXPIRED N > L + T + G + ε Deny access; require re-issuance Request new capability from issuer Absolute expiry; zombie capability prevention
FUTURE N < L − Δ Deny access; report clock inconsistency Check system clock synchronization Prevents timestamp manipulation attacks

The STALE state is the core innovation of Lease-CAP. It provides a bounded grace period for renewal that acknowledges the reality of distributed systems: network partitions happen, issuers may be temporarily unreachable, and controllers cannot always synchronize instantaneously. During this window, verifiers do not immediately deny access; instead, they return a 403 response that includes the sync endpoint and the verifier's current timestamp, enabling the controller to synchronize and retry. This design preserves liveness during temporary network issues without sacrificing security — a capability cannot remain in STALE indefinitely, as the GracePeriod bounds the window.

What Makes This Novel

Lease-CAP introduces the concept of synchronization-bound authority — the principle that authority decays unless actively maintained through cryptographic proof of continued authorization. This is fundamentally different from existing approaches:

Approach Mechanism Liveness Guarantee Revocation Capability Offline Operation
Time-bound tokens (OAuth) Passive decay with absolute expiry None (just expiry) Requires introspection endpoint Limited (no renewal)
Revocation lists Reactive control via lists None (propagation delay) Yes, but propagation is unbounded Unreliable (lists may be stale)
Static capabilities (UCAN, ZCAP-LD) None (immutable assertions) None No Full (unbounded, which is a risk)
Lease-CAP Active synchronization Bounded liveness (T+G window) Yes, bounded by T+G Bounded, explicit opt-in

Beyond the liveness guarantee, Lease-CAP introduces several novel technical contributions:

Relationship to Existing Standards

Lease-CAP is designed as a temporal control layer that can be applied to any capability-based authorization system. Rather than replacing existing standards, it extends them with liveness guarantees:

UCAN (User Controlled Authorization Networks)
Lease-CAP wraps UCAN delegation chains with lease state, adding temporal freshness to UCAN's delegation model. The UCAN's optional expiry field can be set to issuanceDate + maxLifetime as an absolute upper bound, while lease state provides fine-grained liveness control.
ZCAP-LD
Lease-CAP extends ZCAP-LD root capabilities with a lease specification. The ZCAP's invocation mechanism remains unchanged; lease state is carried separately, verified alongside the capability proof.
DIDComm
Lease-CAP sync messages can be wrapped in DIDComm envelopes for agent-to-agent communication. The specification provides guidance on asynchronous sync handling, mediator support, and end-to-end encryption.
OAuth 2.0
Lease-CAP offers a decentralized alternative to token introspection. Instead of a centralized introspection endpoint, issuers provide sync endpoints that controllers contact directly. Revocation is bounded and does not require real-time communication for every access.

Integration Note: When integrating Lease-CAP with existing systems, the lease specification is added as a new property in the capability object. Verifiers that understand Lease-CAP check liveness; those that don't can ignore the lease specification and treat the capability as a traditional static capability (though this is not recommended for production deployments).

Conformance

This specification defines conformance criteria for three distinct roles: Controllers (entities that hold and exercise capabilities), Verifiers (entities that check capability validity), and Issuers (entities that create capabilities and manage sync state). Each role has mandatory and recommended requirements.

RFC 2119 Language

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 and RFC 8174.

Conforming Controller

A Controller is the entity that holds a capability and exercises it to access resources. In the Lease-CAP model, controllers are responsible for maintaining the liveness of their capabilities through periodic synchronization with issuers.

A conforming Controller MUST:

  1. Implement the sync protocol defined in , including the ability to send signed LeaseSyncRequest messages and process LeaseSyncResponse responses.
  2. Respect TTL and GracePeriod values from the issuer as specified in the capability's leaseSpec. Controllers MUST NOT attempt to sync more frequently than the issuer's rate limits, nor less frequently than would cause the capability to expire.
  3. Apply jitter to sync scheduling as defined in to prevent thundering herd problems when large fleets of devices sync simultaneously.
  4. Store and manage capabilities securely, protecting private keys and capability storage from unauthorized access. At minimum, capabilities SHOULD be stored in encrypted form.
  5. Generate valid capability invocation proofs using the capabilityInvocation proof purpose, demonstrating control over the controller's private key.
  6. Never embed lastSync in the Verifiable Credential. The lease state is derived from LeaseSyncResponse objects stored separately. Any controller that embeds timestamps in the credential body is non-conforming.
  7. Maintain a lease state cache keyed by capabilityId, storing the latest valid LeaseSyncResponse for each capability.

A conforming Controller SHOULD:

  1. Perform proactive sync before TTL expiration rather than waiting for STALE responses from verifiers. Proactive sync eliminates the first-request latency penalty and provides a better user experience.
  2. Implement Randomized Truncated Exponential Backoff for sync failures as defined in , preventing network storms during issuer outages.
  3. Log audit events for compliance, including successful syncs, failed sync attempts, and access requests. Audit logs SHOULD include timestamps, capability IDs, and results.
  4. Detect clock drift by comparing local time with verifier timestamps received in 403 responses, and adjust sync timing accordingly.

Multi-device Considerations: When the same capability is held across multiple devices (e.g., a user's phone and laptop), each device maintains its own independent lease state cache and sync schedule. The issuer MUST accept sync requests where previousLastSync does not match the issuer's most recent sync value, as long as it matches a previously issued value (including the initial issuanceDate). This allows each device to sync independently without coordination.

Conforming Verifier

A Verifier is the entity that checks capability validity before granting access to a protected resource. Verifiers are responsible for evaluating lease state, checking revocation status, and enforcing temporal bounds.

A conforming Verifier MUST:

  1. Implement the full state evaluation algorithm defined in , including all state transitions and priority ordering (FUTURE → REVOKED → INVALID → ACTIVE → STALE → EXPIRED).
  2. Enforce the future skew bound Δ configurable per leaseSpec. If now < lastSync − Δ, the capability MUST be treated as FUTURE and rejected, regardless of other conditions.
  3. Validate all cryptographic proofs using the appropriate verification method. Verifiers MUST resolve the issuer's DID to obtain the current verification key and verify the proof according to the Data Integrity specification.
  4. Maintain a revocation cache with expiresAt set to max(revokedAt + TTL + GracePeriod, lastSeenTimestamp + TTL + GracePeriod). This formula ensures the cache entry outlives any capability instance that was valid at the time of revocation, even for verifiers that haven't recently seen the capability.
  5. Reject capabilities where now < lastSync − Δ as FUTURE state, preventing timestamp manipulation attacks where an issuer or attacker sets newLastSync far in the future.
  6. Derive effective lease state exclusively from the latest valid LeaseSyncResponse in the lease state cache. If no valid response exists, use the initial state with lastSync = issuanceDate. Verifiers MUST NOT read timestamp fields from the credential body.
  7. Include the current timestamp in all 403 Sync Required responses using the verifierTimestamp field. This enables controllers to detect clock drift before attempting sync.
  8. Reject any LeaseSyncResponse whose capabilityHash does not match H(canonicalize(capability)). This check prevents substitution attacks where a valid lease state is presented for a different capability.

A conforming Verifier SHOULD:

  1. Use NTP-synchronized wall-clock time with monotonic clocks for drift detection. While absolute time is needed for state evaluation, monotonic clocks provide a tamper-resistant source for measuring intervals.
  2. Implement rate limiting per controller to prevent denial-of-service attacks. The RECOMMENDED rate limit is 10 sync requests per minute with a burst allowance of 30 requests.
  3. Log verification decisions for audit, including the capability ID, controller DID, evaluated state, timestamp, and outcome. Audit logs SHOULD be stored in a tamper-evident manner.
  4. Support offline mode only when explicitly enabled by the issuer in the leaseSpec.offlineMode object. When offline mode is disabled, verifiers MUST deny access if they cannot reach the issuer to verify STALE capabilities.

Conforming Issuer

An Issuer is the entity that creates capabilities and manages their lease state. Issuers are trusted to enforce lease attenuation, maintain revocation state, and provide sync endpoints.

A conforming Issuer MUST:

  1. Provide a sync endpoint as defined in that accepts LeaseSyncRequest messages and returns signed LeaseSyncResponse messages.
  2. Sign all sync responses with capabilityAssertion proofs using the issuer's private key. The signature MUST be verifiable using the verification method referenced in the capability credential.
  3. Enforce lease attenuation: for any delegated capability, T_child + G_child ≤ T_parent + G_parent MUST hold. Issuers MUST NOT issue child capabilities that could outlive their parent's absolute expiration boundary.
  4. Maintain capability state including revocation status, delegation chains, and historical newLastSync values (for at least TTL + GracePeriod to support multi-device scenarios).
  5. Verify controller identity before issuing sync responses by checking the capabilityInvocation proof in the sync request. Issuers MUST ensure the request signer matches the controller DID in the capability.
  6. Not issue a successful LeaseSyncResponse for a revoked capability. If a capability is revoked, the issuer MUST return a response with status: "revoked".
  7. Include status = "revoked" in sync responses for revoked capabilities, along with revokedAt timestamp and revocation reason. The response MUST be signed normally.
  8. Support configurable Δ per leaseSpec to accommodate different network environments (e.g., 5 seconds for web applications, 30 seconds for satellite links). When not specified, Δ defaults to 5000 ms.
  9. Verify that a parent capability is in ACTIVE state before renewing a child capability. If the parent is STALE or EXPIRED, the issuer MUST NOT issue a successful sync response for the child.
  10. Remember previously issued newLastSync values for at least TTL + GracePeriod to support multi-device scenarios where one device's previousLastSync may be older than the most recent sync issued to another device.

A conforming Issuer SHOULD:

  1. Implement rate limiting per controller to prevent abuse. The RECOMMENDED limit is 10 sync requests per minute with a burst of 30.
  2. Support batch sync where a single request can update multiple capabilities simultaneously, improving privacy and efficiency.
  3. Provide revocation endpoints allowing controllers or administrators to request revocation of compromised capabilities.
  4. Monitor for thundering herd patterns where many devices sync simultaneously, and adjust sync response timing or rate limits accordingly.

Security Note on Parent State Verification: The requirement to verify parent ACTIVE state before renewing a child capability introduces a potential race condition: a parent could expire between the check and the child's next use. To mitigate this, issuers SHOULD include the parent's lastSync value in the child's sync response, or verifiers SHOULD re-check parent state during delegation chain verification (as specified in Algorithm 11.5).

Terminology

This section defines terms used throughout the specification. Readers familiar with Verifiable Credentials, DID Core, and capability-based authorization systems will recognize many concepts; Lease-CAP introduces new terminology around temporal state and synchronization.

Core Terms

Liveness-Bound Capability (LBC)
A capability whose validity requires periodic synchronization with the issuer. Unlike static capabilities that remain valid until revocation or expiry, LBCs decay over time unless actively maintained.
Lease
The temporal validity period of a capability, defined by the combination of TTL and GracePeriod. The lease determines how long a capability remains ACTIVE after synchronization and how long it can be renewed.
TTL (Time-To-Live)
The duration in seconds that a capability remains in the ACTIVE state after the lastSync timestamp. During this period, verifiers grant access without requiring issuer contact.
GracePeriod
The duration in seconds after TTL expiration during which synchronization can still succeed. Capabilities in the STALE state are not granted access until sync completes, but they have not yet expired.
lastSync
The newLastSync timestamp from the latest valid LeaseSyncResponse for a capability. This value determines the starting point for TTL and GracePeriod calculations.
Effective Lease State
The current validity state (ACTIVE, STALE, EXPIRED, FUTURE, REVOKED, or INVALID) derived from the latest valid LeaseSyncResponse and the current time.
Controller
The entity that holds a capability and exercises it to access resources. Controllers are responsible for maintaining liveness through synchronization.
Issuer
The entity that creates and signs the capability credential and sync responses. Issuers manage lease state and enforce revocation.
Verifier
The entity that checks capability validity before granting resource access. Verifiers evaluate lease state, check revocation, and enforce temporal bounds.
Liveness Guarantee
The property that a capability remains valid only while the controller successfully synchronizes within bounded intervals. Formally, a capability is live at time t if there exists a valid sync response with newLastSync > t - (T + G).
Lease State Cache
The verifier- or controller-side store of received LeaseSyncResponse objects, keyed by capabilityId. The cache holds the most recent lease state for each capability.

State Definitions

The following parameters define the temporal state of a capability. All time values are expressed in milliseconds since the Unix epoch (1970-01-01T00:00:00Z), unless otherwise specified.

Let:

The four temporal states are mutually exclusive and evaluated in the following priority order:

  1. FUTURE: N < L − Δ — The lastSync timestamp is too far in the future relative to the verifier's clock, indicating possible clock manipulation or an issuer error.
  2. REVOKED: The issuer has explicitly revoked the capability (checked separately via revocation cache).
  3. INVALID: The capability proof is invalid or the controller does not match.
  4. ACTIVE: L − Δ ≤ N ≤ L + T + ε — The capability is within its TTL window and can be used normally.
  5. STALE: L + T + ε < N ≤ L + T + G + ε — The TTL has elapsed but the capability is within the grace period; sync is required before access.
  6. EXPIRED: N > L + T + G + ε — The capability has exceeded both TTL and GracePeriod; it cannot be renewed and requires re-issuance.

On Clock Tolerance ε: The clock tolerance ε is applied symmetrically to both the ACTIVE/STALE boundary and the STALE/EXPIRED boundary. A capability with N = L + T + ε is ACTIVE; at N = L + T + ε + 1ms it becomes STALE. This provides symmetric handling of clock skew without creating overlapping state regions or gaps.

On Future Skew Bound Δ: The Δ parameter prevents attacks where an issuer or replay attack sets newLastSync far in the future, effectively bypassing expiration. If the verifier's clock is properly synchronized, Δ should be set to a small value (e.g., 5 seconds). For high-latency environments (e.g., satellite communications), Δ may be increased to accommodate legitimate clock differences.

Notation

The following notation is used throughout this specification:

SymbolMeaningExample
||Concatenation of byte strings"abc" || "def" = "abcdef"
H(x)SHA-256 hash of x, returned as hex-encoded stringH("hello") = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
Sign(k, m)Digital signature of message m with key k using Ed25519Sign(privateKey, canonicalDoc)
[x]Optional element; may be present or absent"proof": { ... } is optional in some contexts
canonicalize(x)JSON Canonicalization Scheme (RFC 8785) applied to xcanonicalize({"a":1,"b":2}) produces deterministic output
base58btc(s)Base58 Bitcoin encoding of byte string sUsed for proof values in Data Integrity proofs

Liveness Property

The liveness property is the fundamental contribution of Lease-CAP. In distributed systems, liveness guarantees that something good eventually happens — in this case, that an authorized controller can eventually gain access to a resource. Safety guarantees that something bad never happens — that unauthorized access never occurs. Lease-CAP provides both.

Definition

A capability is considered live if and only if the controller can successfully synchronize with the issuer within intervals bounded by TTL and GracePeriod. Formally:

Live(capability, t) ⇔ ∃ valid LeaseSyncResponse R such that R.newLastSync > t − (T + G)

This definition captures the intuition that a live capability is one where the controller has successfully maintained the authorization relationship within a bounded time window of length T + G before time t. The issuer's memory of successful syncs anchors the liveness property.

Note that liveness is distinct from safety: a capability could be safe (not usable by unauthorized parties) but not live (the authorized controller cannot use it due to sync failures). The STALE state is specifically designed to trade off liveness against safety during network partitions.

Liveness Guarantee

Theorem (Liveness Guarantee): For any capability that has not been revoked, there exists a bounded time window [lastSync, lastSync + T + G] during which a controller who can successfully synchronize will be granted access. Outside this window, the capability is EXPIRED regardless of controller intent.

This guarantee has several important consequences:

The guarantee is bounded because the issuer cannot guarantee liveness indefinitely — if the controller cannot reach the issuer, eventually the capability will expire. This is an intentional design choice: unbounded offline operation is a security risk, as it allows capabilities to be used long after they should be invalid.

Relationship to Safety

Safety and liveness are complementary properties. In Lease-CAP:

PropertyDefinitionLease-CAP MechanismBound
Safety (no unauthorized access) Unauthorized parties cannot access protected resources Revocation + cryptographic expiry + proof verification Absolute: access requires valid proofs and non-expired state
Liveness (authorized access eventually possible) Authorized controllers can eventually gain access Sync protocol + STALE grace period Temporary: depends on network connectivity and issuer availability

The STALE state represents the trade-off between these properties. During network partitions, liveness may be temporarily sacrificed (access is blocked until sync completes) to preserve safety (a capability that cannot sync will eventually expire rather than remaining usable indefinitely). This trade-off is controlled by the gracePeriod parameter, which can be tuned based on expected network reliability.

Formal Property: Lease-CAP provides eventual liveness under the assumption of eventual issuer reachability. Formally: if a capability is not revoked and the issuer becomes reachable within some finite time δ, then there exists a time t ≤ lastSync + T + G + δ such that the controller can sync and regain access. The worst-case liveness delay is δ + (network round-trip time).

Security Model

The security model defines the capabilities of attackers, the security goals of the system, and the trust assumptions required for correct operation. This model informs the design decisions throughout the specification.

Attacker Capabilities

The security model assumes an attacker who can:

  1. Compromise controllers: steal private keys or capability storage, either through software vulnerabilities or physical access.
  2. Observe network traffic: eavesdrop on sync requests and responses, gaining knowledge of which capabilities are being exercised and when.
  3. Replay messages: capture and retransmit old sync responses, potentially attempting to extend capability lifetime.
  4. Manipulate clocks: adjust system time within bounds, either on the controller, verifier, or issuer, to attempt to extend or shrink temporal windows.
  5. Control network partitions: block access to issuers temporarily, either through network-level attacks or by compromising network infrastructure.
  6. Launch denial-of-service attacks: flood sync endpoints with requests, potentially overwhelming issuers.

The attacker CANNOT:

  1. Compromise the issuer's private key — this would allow the attacker to issue arbitrary sync responses and break all security properties.
  2. Break cryptographic primitives (Ed25519 for signatures, SHA-256 for hashing) — these are assumed to be secure under standard cryptographic assumptions.
  3. Undetectably modify signed messages — any modification to a signed message will cause signature verification to fail.
  4. Control NTP-synchronized clocks on honest verifiers — verifiers are assumed to maintain accurate time through trusted NTP sources or hardware clocks.

These attacker capabilities represent a realistic threat model for decentralized systems. The security mitigations are designed to remain effective even when multiple attacker capabilities are combined.

Security Goals

Lease-CAP is designed to achieve the following security goals, each of which is formally defined and verified in the security analysis:

  1. Freshness: Capabilities cannot be used beyond their intended temporal window. More formally: for any capability usage at time t, there must exist a valid sync response with newLastSync > t - (T + G). This bounds the "zombie capability" window.
  2. Revocability: Issuers can revoke capabilities with bounded propagation delay. When an issuer marks a capability as revoked, all verifiers will reject that capability within at most T + G time units. This bound is tight — after T + G, the capability would have expired anyway.
  3. Delegation safety: Delegated capabilities cannot exceed parent authority or lifetime. For any valid delegation chain, the lease parameters must satisfy T_child + G_child ≤ T_parent + G_parent. This ensures the child capability cannot outlive its parent.
  4. Replay resistance: Captured sync responses cannot extend capability lifetime beyond intended bounds. The combination of strictly increasing timestamps, nonce binding, and previousLastSync chaining prevents replay attacks.
  5. Offline safety: Offline mode provides bounded grace without compromising security. When offline mode is enabled, capabilities can be used for at most maxDurationSeconds without issuer contact, and the graceMultiplier is bounded by 2.0.
  6. Binding integrity: Lease state cannot be substituted across different capability credentials. The capabilityHash field in every sync response cryptographically binds the lease state to a specific capability credential.

Trust Assumptions

Lease-CAP operates under the following trust assumptions. These assumptions must be satisfied for the security guarantees to hold:

  1. Issuers are trusted to enforce lease attenuation, revocation, and parent-state checks. A malicious issuer could violate these properties, but such behavior would be detectable through audit logs and would violate the issuer's reputation. In decentralized systems, trust in issuers is typically established through DID-based identities and verifiable credentials.
  2. NTP-synchronized clocks are available on verifiers (hardware-backed where required by policy). Clock manipulation is a primary attack vector; verifiers must maintain accurate time. High-security deployments SHOULD use hardware security modules (HSMs) or trusted platform modules (TPMs) for timekeeping.
  3. Cryptographic keys are stored securely (HSM or equivalent for high-security deployments). Key compromise is catastrophic; controllers and issuers must protect private keys accordingly.
  4. TLS provides channel security (defense in depth alongside cryptographic proofs). While cryptographic proofs provide message integrity and authenticity, TLS protects against traffic analysis and provides forward secrecy.
  5. Verifiers correctly implement the state evaluation algorithm and enforce all bounds. A faulty verifier could grant inappropriate access or deny legitimate access.

Trust Minimization: Lease-CAP minimizes trust requirements compared to centralized systems. Verifiers do not need to trust issuers beyond verifying cryptographic proofs. Controllers do not need to trust verifiers beyond accepting signed responses. The only entity that requires significant trust is the issuer, which is identified by a DID and can be held accountable through verifiable evidence.

Threat Model Summary

The following table summarizes key threats, their mitigations, and the sections where mitigations are specified:

ThreatMitigationSection
Zombie capabilitiesNatural expiration (T + G) bounds worst-case lifetime
Clock manipulationε + Δ bounds, NTP synchronization, monotonic clocks
Replay attacksStrictly increasing timestamps + nonce binding + previousLastSync chain + issuer historical state
Capability substitutioncapabilityHash binding in every sync response
Revocation raceIssuer refuses sync for revoked caps; verifier caches revocation proofs with extended expiry
Denial of serviceRate limiting + randomized truncated exponential backoff
Key compromiseRevocation endpoint + cache; key rotation via re-issuance
Thundering herdJitter + truncated exponential backoff + load-aware sync window assignment
Offline abuseBounded grace + audit + multiplier limit + explicit issuer opt-in
Lease attenuation bypassT_child + G_child ≤ T_parent + G_parent enforced by issuer
Parent expiry bypassIssuer checks parent ACTIVE state before renewing child

Core Concepts

The following concepts form the foundation of Lease-CAP. Understanding these concepts is essential for implementing conforming systems.

State Separation: Static Credential and Dynamic Lease State

A Liveness-Bound Capability consists of two strictly separated components. This separation is a fundamental architectural decision that enables efficient state management, prevents forgery, and supports multi-device scenarios.

Static Capability Credential (immutable): A W3C Verifiable Credential containing fixed properties that never change throughout the capability's lifetime:

Once issued, this document MUST NOT change. Any modification would invalidate the issuer's signature and break the cryptographic binding to lease state.

Dynamic Lease State (mutable, issuer-signed): A LeaseSyncResponse object that captures the current synchronization state of the capability:

This object is NOT embedded in the credential. It is stored separately by controllers and verifiers, updated each time a successful sync occurs.

Critical Implementation Requirement: The lastSync value MUST NOT appear in the Verifiable Credential body. Any implementation that embeds lastSync inside credentialSubject is non-conforming. Embedding timestamps in the credential would require re-issuing the credential on every sync, defeating the purpose of state separation, and would allow controllers to forge lease state by presenting a self-constructed credential. Verifiers MUST derive the effective lease state from the latest valid LeaseSyncResponse stored in their lease state cache.

Effective Lease State

The Effective Lease State of a capability is determined by the latest valid LeaseSyncResponse in the lease state cache:

EffectiveLeaseState(capabilityId) = entry in LeaseStateCache with largest newLastSync
                                    that passes proof verification and capabilityHash check

If no valid LeaseSyncResponse exists in the cache, the initial lease state is:

This means a freshly issued capability is immediately usable without a prior sync, for up to TTL seconds from issuance. This design choice enables smooth onboarding and reduces latency for first use.

Verifiers and controllers maintain their own caches, which may diverge temporarily. This is acceptable because:

Cryptographic Binding Between Credential and Lease State

To prevent substitution attacks — where an attacker presents a valid lease state for a different capability with the same ID — each LeaseSyncResponse MUST include a hash of the original capability credential:

{
  "capabilityId": "urn:cap:9f8e7d6c...",
  "capabilityHash": "H(canonicalize(capability))",
  "newLastSync": "...",
  "proof": { ... }
}

Verifiers MUST check:

H(canonicalize(capability)) == response.capabilityHash

If the check fails, the sync response MUST be rejected and the effective lease state MUST NOT be updated.

Attack Scenario Prevented: Without this binding, an attacker could:

  1. Obtain a valid LeaseSyncResponse for capability A (which they legitimately control).
  2. Create a new capability B with the same capabilityId but a different controller DID.
  3. Present the sync response for A as if it applied to B, potentially gaining access with B using A's lease state.

The capabilityHash prevents this by ensuring each sync response is cryptographically bound to a specific credential. Since the hash includes the controller DID and all other fields, any change to the credential results in a different hash, causing verification to fail.

Lease Attenuation and Delegation

Capability delegation is a powerful feature of capability-based systems. Lease-CAP supports delegation with the important constraint of lease attenuation: a delegated capability cannot have a longer effective lifetime than its parent.

When delegating capabilities, the following MUST hold:

T_child + G_child ≤ T_parent + G_parent

A child capability MUST NOT outlive the absolute expiration boundary of its parent. This preserves the principle of attenuation of authority: a delegate cannot receive more authority than the delegator possesses.

Child Sync Independence: A child capability's lastSync can be updated independently of its parent. The child maintains its own lease state cache and sync schedule. However, the issuer MUST verify that the parent capability is in ACTIVE state at the time of the child's renewal request. If the parent is STALE or EXPIRED, the issuer MUST NOT issue a successful sync response for the child.

Delegation Chain Depth: Implementations MUST enforce a configurable maximum delegation depth. The RECOMMENDED maximum depth is 5 levels. Chains exceeding this depth MUST be rejected. This prevents unbounded recursion and potential denial-of-service attacks through deep delegation chains.

Example:

Synchronization-Bound Authority

Lease-CAP's fundamental contribution is that authority decays unless actively maintained. This represents a paradigm shift from static to dynamic authorization. To understand why this matters, consider the differences:

This synchronization-bound authority has several benefits:

  1. Automatic expiration: Even without explicit revocation, capabilities expire naturally if not maintained.
  2. Freshness verification: Verifiers can be confident that recently-synced capabilities represent current authorization.
  3. Compromise recovery: If a controller is compromised, the attacker's access is limited to at most T + G from the last sync.
  4. Audit visibility: Sync events provide a verifiable audit trail of continued authorization.

Future State Protection

To prevent attacks where an issuer or replay sets newLastSync far in the future — effectively bypassing expiration — verifiers MUST enforce a maximum future skew bound Δ. If N < L − Δ, the capability MUST be treated as FUTURE and rejected.

Attack Scenario: Without FUTURE protection, an attacker could:

  1. Compromise an issuer or intercept a sync response.
  2. Modify the newLastSync timestamp to a value far in the future (e.g., year 2030).
  3. Present this modified response to verifiers, who would see a valid signature and treat the capability as ACTIVE for years.

Mitigation: The FUTURE state rejects any capability where the verifier's current time is more than Δ behind lastSync. Since Δ is typically small (5 seconds for most applications), any timestamp significantly in the future is rejected.

The value of Δ SHOULD be configurable per leaseSpec to accommodate different environments:

When not specified, Δ defaults to 5000 ms (5 seconds).

Temporal Coupling Across Delegation Chains

During verification of a delegation chain, each capability in the chain MUST be evaluated at the same reference time N. If any capability in the chain resolves to STALE or EXPIRED, the entire chain MUST be treated as invalid for the purpose of granting access, unless the controller performs sync and retries.

This temporal coupling ensures that a controller cannot bypass the liveness requirement by using a stale parent capability to authorize a child. The entire delegation chain must be fresh at the time of access.

Example:

The root capability's TTL expired at 2024-01-16T10:00:00Z. Even though the child's TTL (from its 12:00 sync) expires at 2024-01-16T00:00:00Z, both are evaluated at the same time. The root is STALE/EXPIRED (depending on whether within grace), so the entire chain is invalid.

Data Model

The Lease-CAP data model defines the structure of capability credentials, sync messages, and cache entries. All messages are JSON objects that can be signed using W3C Data Integrity proofs.

Capability Credential (Static, Immutable)

The Capability Credential is a W3C Verifiable Credential with additional context for lease specifications. The leaseSpec field carries all lease parameters needed by both controllers and verifiers. Note that lastSync does NOT appear here — it is part of the dynamic lease state.

{
  "@context": [
    "https://www.w3.org/ns/credentials/v2",
    "https://w3id.org/lease-cap/v1"
  ],
  "id": "urn:cap:9f8e7d6c-4b3a-2c1d-8e7f-6a5b4c3d2e1f",
  "type": ["VerifiableCredential", "LeaseCapability"],
  "issuer": "did:key:z6MkhaXgBcjvVgJKKKZo9vQqYhF8JxqB3d5hL5xK5X5x5X",
  "issuanceDate": "2024-01-15T10:00:00Z",
  "credentialSubject": {
    "id": "did:key:z6MkhaXgBcjvVgJKKKZo9vQqYhF8JxqB3d5hL5xK5X5x5Y",
    "capability": {
      "invocationTarget": "https://storage.example.com/api/v1/buckets/user-123",
      "allowedActions": ["read", "write", "list"],
      "leaseSpec": {
        "ttl": 86400,
        "gracePeriod": 300,
        "futureSkewBound": 5000,
        "syncEndpoint": "https://issuer.example.com/api/v1/capabilities/sync",
        "syncMethod": "POST",
        "offlineMode": {
          "enabled": false
        }
      },
      "caveats": [
        {
          "type": "ExpiresAt",
          "value": "2025-12-31T23:59:59Z"
        },
        {
          "type": "RateLimit",
          "maxRequests": 1000,
          "window": 3600
        }
      ]
    }
  },
  "proof": {
    "type": "DataIntegrityProof",
    "cryptosuite": "eddsa-2022",
    "created": "2024-01-15T10:00:00Z",
    "verificationMethod": "did:key:z6MkhaXgBcjvVgJKKKZo9vQqYhF8JxqB3d5hL5xK5X5x5X#z6MkhaXgBcjvVgJKKKZo9vQqYhF8JxqB3d5hL5xK5X5x5X",
    "proofPurpose": "capabilityDelegation",
    "proofValue": "z5hRKZ..."
  }
}

Key points about the credential structure:

Lease Sync Response (Dynamic, Issuer-Signed)

This object is returned by the issuer at sync time. It is stored in the verifier's and/or controller's lease state cache. It is the sole authoritative source of lastSync.

{
  "type": "LeaseSyncResponse",
  "capabilityId": "urn:cap:9f8e7d6c-4b3a-2c1d-8e7f-6a5b4c3d2e1f",
  "capabilityHash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  "previousLastSync": "2024-01-14T10:00:00Z",
  "newLastSync": "2024-01-15T10:00:00Z",
  "nextSyncRecommended": "2024-01-16T10:00:00Z",
  "nonce": "9f8e7d6c-4b3a-2c1d-8e7f-6a5b4c3d2e1f",
  "status": "active",
  "proof": {
    "type": "DataIntegrityProof",
    "cryptosuite": "eddsa-2022",
    "created": "2024-01-15T10:00:01Z",
    "verificationMethod": "did:key:z6MkhaXgBcjvVgJKKKZo9vQqYhF8JxqB3d5hL5xK5X5x5X#z6MkhaXgBcjvVgJKKKZo9vQqYhF8JxqB3d5hL5xK5X5x5X",
    "proofPurpose": "capabilityAssertion",
    "proofValue": "z4sJZk..."
  }
}

Field descriptions:

Sync Request

{
  "type": "LeaseSyncRequest",
  "capabilityId": "urn:cap:9f8e7d6c-4b3a-2c1d-8e7f-6a5b4c3d2e1f",
  "lastKnownSync": "2024-01-15T10:00:00Z",
  "nonce": "4b3a2c1d-8e7f-6a5b-4c3d-2e1f0a9b8c7d",
  "proof": {
    "type": "DataIntegrityProof",
    "cryptosuite": "eddsa-2022",
    "created": "2024-01-16T09:00:00Z",
    "verificationMethod": "did:key:z6MkhaXgBcjvVgJKKKZo9vQqYhF8JxqB3d5hL5xK5X5x5Y#z6MkhaXgBcjvVgJKKKZo9vQqYhF8JxqB3d5hL5xK5X5x5Y",
    "proofPurpose": "capabilityInvocation",
    "proofValue": "z7tNQp..."
  }
}

lastKnownSync MUST be set to the newLastSync value of the controller's current lease state. On first sync (no prior LeaseSyncResponse), it MUST be set to the capability's issuanceDate.

403 Sync Required Response

When a capability is STALE, verifiers MUST return a 403 with the following body. The verifierTimestamp field allows the controller to detect clock drift before attempting sync.

{
  "error": "sync_required",
  "syncEndpoint": "https://issuer.example.com/api/v1/capabilities/sync",
  "verifierTimestamp": "2024-01-16T10:05:00Z",
  "reason": "Capability is in STALE state. TTL expired at 2024-01-16T10:00:00Z, within grace period until 2024-01-16T10:05:00Z."
}

Revoked Sync Response

When the issuer has revoked the capability, it MUST return this structure in response to any sync request. The controller and verifier MUST cache this revocation.

{
  "type": "LeaseSyncResponse",
  "capabilityId": "urn:cap:9f8e7d6c-4b3a-2c1d-8e7f-6a5b4c3d2e1f",
  "capabilityHash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  "status": "revoked",
  "revokedAt": "2024-01-15T15:30:00Z",
  "reason": "Key compromise reported",
  "proof": {
    "type": "DataIntegrityProof",
    "cryptosuite": "eddsa-2022",
    "created": "2024-01-15T15:30:01Z",
    "verificationMethod": "did:key:...",
    "proofPurpose": "capabilityAssertion",
    "proofValue": "z9rMqX..."
  }
}

Revocation Cache Entry

{
  "capabilityId": "urn:cap:9f8e7d6c-4b3a-2c1d-8e7f-6a5b4c3d2e1f",
  "revokedAt": "2024-01-15T15:30:00Z",
  "expiresAt": "2024-01-17T15:30:00Z",
  "lastSeenTimestamp": "2024-01-15T14:00:00Z",
  "proof": { ... }
}

The expiresAt field MUST be set to max(revokedAt + TTL + GracePeriod, lastSeenTimestamp + TTL + GracePeriod). This ensures the cache entry outlives any capability instance that was valid at the time of revocation, even for verifiers that haven't recently seen the capability.

Rationale: If expiresAt were set to revokedAt + TTL + GracePeriod only, a verifier that last saw the capability at time lastSeen (where lastSeen > revokedAt) might have a cache entry that expires before the verifier learns of the revocation through a sync attempt. Using max() ensures the entry persists for a full T + G window beyond the last time the verifier saw the capability, guaranteeing that any subsequent access attempt will hit the revocation cache.

Implementations SHOULD run periodic cleanup to remove entries where now > expiresAt.

Offline Mode Configuration

When the issuer permits offline operation, the offlineMode object in leaseSpec MUST include:

"offlineMode": {
  "enabled": true,
  "maxDurationSeconds": 172800,
  "graceMultiplier": 1.5
}

The effective offline expiry is:

offlineExpiry = min(
  lastSync + TTL + (GracePeriod × graceMultiplier),
  lastSync + maxDurationSeconds
)

When offlineMode.enabled is false or the field is absent, verifiers MUST deny access if they cannot reach the issuer. Verifiers MUST NOT apply any offline grace extension in this case.

State Machine

The capability state machine defines how capabilities transition between states over time and in response to events. Understanding these transitions is essential for implementing correct verifiers and controllers.

State Transition Table

The following table enumerates all possible state transitions, the events that trigger them, and the conditions that must hold:

Current State Event Next State Condition Example
NONE (not yet issued) ISSUE ACTIVE Valid issuer signature; initial lastSync = issuanceDate Issuer creates and signs capability credential
ACTIVE TIME_ADVANCE STALE N > L + T + ε (TTL expired, within grace) 24 hours after last sync, capability becomes stale
ACTIVE SYNC_SUCCESS ACTIVE Valid sync response received; lastSync updated Controller syncs before TTL expires
ACTIVE REVOKE REVOKED Valid revocation proof received Issuer revokes capability
STALE SYNC_SUCCESS ACTIVE Valid sync response received within grace period Controller syncs after TTL expires but before grace ends
STALE TIME_ADVANCE EXPIRED N > L + T + G + ε (grace period elapsed) 5 minutes after STALE, capability expires
EXPIRED RE_ISSUE ACTIVE New capability credential issued by issuer Issuer issues new capability with fresh issuance date
ANY FUTURE_DETECT FUTURE N < L − Δ (lastSync too far in future) Verifier detects timestamp in future beyond Δ
REVOKED REVOKED Terminal state No further transitions; re-issuance required

State Diagram

                                    ┌─────────────────────────────────────┐
                                    │                                     │
                                    ▼                                     │
                         Issue/Delegate                                   │
    ┌──────┐  ─────────────────────────────►  ┌──────────┐                │
    │ NONE │                                   │  ACTIVE  │ ◄──────────────┘
    └──────┘                                   └──────────┘                │
                                                    │                      │
                                          N > L+T+ε │               SYNC_SUCCESS
                                                    ▼                      │
                                               ┌─────────┐                 │
                                               │  STALE  │ ────────────────┘
                                               └─────────┘                 │
                                                    │                      │
                                       N > L+T+G+ε │                      │
                                                    ▼                      │
                                               ┌─────────┐                 │
                                               │ EXPIRED │                 │
                                               └─────────┘                 │
                                                    │                      │
                                              RE_ISSUE │                   │
                                                    ▼                      │
                                               ┌─────────┐                 │
                                               │  ACTIVE │ ────────────────┘
                                               └─────────┘

    From ACTIVE or STALE:  REVOKE ──► REVOKED (terminal)
    From ANY:              N < L−Δ ──► FUTURE (reject)

    LEGEND:
    ───► Normal transition
    ◄──► Bidirectional (sync success returns to ACTIVE)
    N = current time, L = lastSync, T = TTL, G = GracePeriod, ε = clockTolerance, Δ = futureSkewBound

Key observations about the state diagram:

Protocol Flows

The protocol flows illustrate the interactions between Controllers, Verifiers, and Issuers in various scenarios. All messages are cryptographically signed and include appropriate proofs.

Normal Operation (ACTIVE)

Controller          Verifier            Issuer
    │                   │                  │
    │  Access request   │                  │
    │  (capability +    │                  │
    │   latest sync     │                  │
    │   response)       │                  │
    │──────────────────►│                  │
    │                   │                  │
    │                   │ 1. Resolve       │
    │                   │    EffectiveState│
    │                   │    from cache    │
    │                   │                  │
    │                   │ 2. Check N ≤ L+T+ε│
    │                   │    → ACTIVE      │
    │                   │                  │
    │  200 OK           │                  │
    │◄──────────────────│                  │
    │                   │                  │
    │  [Resource access │                  │
    │   granted]        │                  │

Description: In normal operation, the controller presents both the capability credential and the latest LeaseSyncResponse. The verifier checks that the capability is in ACTIVE state (current time within TTL of lastSync) and grants access. No issuer contact is required, enabling low-latency operation.

STALE State Flow (Sync Required)

Controller          Verifier            Issuer
    │                   │                  │
    │  Access request   │                  │
    │──────────────────►│                  │
    │                   │                  │
    │                   │ State → STALE    │
    │                   │ (TTL expired,    │
    │                   │  within grace)   │
    │                   │                  │
    │  403 + verifier   │                  │
    │  timestamp        │                  │
    │◄──────────────────│                  │
    │                   │                  │
    │  Detect drift     │                  │
    │  (compare         │                  │
    │   timestamps)     │                  │
    │                   │                  │
    │  LeaseSyncRequest │                  │
    │  (with nonce)     │                  │
    │─────────────────────────────────────►│
    │                   │                  │
    │                   │                  │ 1. Verify proof
    │                   │                  │ 2. Check controller
    │                   │                  │ 3. Check parent ACTIVE
    │                   │                  │ 4. Issue new timestamp
    │                   │                  │
    │  LeaseSyncResponse│                  │
    │  (newLastSync)    │                  │
    │◄─────────────────────────────────────│
    │                   │                  │
    │  Update cache     │                  │
    │                   │                  │
    │  Retry request    │                  │
    │  (updated lease   │                  │
    │   state)          │                  │
    │──────────────────►│                  │
    │                   │                  │
    │                   │ State → ACTIVE   │
    │                   │ (new lastSync    │
    │                   │  within TTL)     │
    │                   │                  │
    │  200 OK           │                  │
    │◄──────────────────│                  │

Description: When a capability is STALE, the verifier returns a 403 response with the sync endpoint and its current timestamp. The controller detects clock drift (if any), then sends a sync request to the issuer. The issuer verifies the request, updates the lease state, and returns a new LeaseSyncResponse. The controller retries the original request with the updated lease state, which is now ACTIVE.

Proactive Sync Flow (RECOMMENDED)

Controller                              Issuer
    │                                      │
    │  Calculate sync time                 │
    │  = TTL × syncLeadTime ± jitter       │
    │                                      │
    │  [wait for scheduled time]           │
    │                                      │
    │  LeaseSyncRequest                    │
    │  (proactive, before TTL expiry)      │
    │─────────────────────────────────────►│
    │                                      │
    │                                      │ Verify, update state
    │                                      │
    │  LeaseSyncResponse                   │
    │  (newLastSync = now)                 │
    │◄─────────────────────────────────────│
    │                                      │
    │  Update lease state cache            │
    │                                      │
    │  [Capability remains ACTIVE          │
    │   without service interruption]      │

Description: Proactive sync is the recommended pattern. The controller calculates a sync time before TTL expiration (e.g., at 80% of TTL) with jitter to avoid thundering herds. By syncing proactively, the controller ensures the capability never enters the STALE state, eliminating the first-request latency penalty.

Offline Mode Flow

Controller          Verifier            Issuer
    │                   │                  │
    │  Access request   │                  │
    │──────────────────►│                  │
    │                   │                  │
    │                   │─── Sync check ──►│ [unreachable]
    │                   │                  │
    │                   │ Check offline    │
    │                   │ config:          │
    │                   │ • enabled? yes   │
    │                   │ • within         │
    │                   │   maxDuration?   │
    │                   │                  │
    │  200 OK           │                  │
    │  (offline warning │                  │
    │   header)         │                  │
    │◄──────────────────│                  │
    │                   │                  │
    │  Background retry │                  │
    │  (exp. backoff)   │                  │
    │─────────────────────────────────────►│
    │                   │                  │
    │                   │                  │ [eventually reachable]
    │                   │                  │
    │  LeaseSyncResponse│                  │
    │◄─────────────────────────────────────│

Description: When offline mode is enabled and the issuer is unreachable, the verifier grants access with a warning header, provided the capability is within the offline expiry window (lastSync + TTL + (GracePeriod × graceMultiplier) and maxDurationSeconds has not been exceeded). The controller continues background sync attempts with exponential backoff.

Multi-Device Sync

Device A          Device B          Issuer
    │                 │                 │
    │  Sync at t=10   │                 │
    │─────────────────────────────────►│
    │                 │                 │
    │                 │                 │ Record: lastSync=10
    │                 │                 │ for capability C
    │  Response (L=10)│                 │
    │◄─────────────────────────────────│
    │                 │                 │
    │                 │  Sync at t=12   │
    │                 │  prevSync=10    │
    │                 │────────────────►│
    │                 │                 │
    │                 │                 │ Accept (prevSync in history)
    │                 │                 │ Record: lastSync=12
    │                 │                 │
    │                 │  Response (L=12)│
    │                 │◄────────────────│
    │                 │                 │
    │  Access at t=13 │                 │
    │  presents L=10  │                 │
    │────────────────►│                 │
    │                 │                 │
    │  [Verifier: L=10 is valid,        │
    │   within TTL, grants access]      │

Description: Multi-device scenarios are explicitly supported. Each device maintains its own lease state cache and syncs independently. The issuer remembers previously issued newLastSync values for at least TTL + GracePeriod. When Device B syncs with previousLastSync=10 (issued to Device A), the issuer accepts the request because 10 is in its history. Device A continues to use its own lease state (L=10) without needing to know about Device B's newer sync.

Cryptographic Proofs

Lease-CAP uses W3C Data Integrity proofs with the Ed25519 cryptosuite for all signed messages. This section defines the proof purposes, generation process, and verification requirements.

Proof Types and Purposes

Proof PurposeUsed BySignsWhen Used
capabilityDelegation Issuer Capability credential at issuance When creating a new capability
capabilityInvocation Controller Sync request; resource access request (if signed) When requesting sync or accessing resources
capabilityAssertion Issuer LeaseSyncResponse; revocation notices When responding to sync requests or revoking capabilities

Key rotation note: Key rotation is not explicitly defined as a proof purpose in this version. Implementations requiring key rotation SHOULD re-issue the capability with a new verificationMethod rather than attempting to rotate keys in place.

Proof Generation

The following algorithm demonstrates proof generation for any document:

function generateProof(
  document: object,
  privateKey: CryptoKey,
  purpose: ProofPurpose,
  verificationMethod: string
): Proof {
  // Remove any existing proof field
  const docWithoutProof = { ...document, proof: undefined };
  
  // Canonicalize the document
  const canonicalDoc = canonicalize(docWithoutProof);
  
  // Create proof configuration
  const proofConfig = {
    type: "DataIntegrityProof",
    cryptosuite: "eddsa-2022",
    created: new Date().toISOString(),
    verificationMethod: verificationMethod,
    proofPurpose: purpose,
    proofValue: ""
  };
  
  // Canonicalize proof config without the proofValue
  const canonicalProofConfig = canonicalize({
    ...proofConfig,
    proofValue: undefined
  });
  
  // Hash the concatenation
  const toSign = hash(canonicalProofConfig + canonicalDoc);
  
  // Sign and encode
  const signature = await sign(privateKey, toSign);
  proofConfig.proofValue = base58btc(signature);
  
  return proofConfig;
}

Proof Verification

async function verifyProof(
  document: object,
  proof: Proof,
  publicKey: CryptoKey
): Promise<boolean> {
  // Remove proof from document for canonicalization
  const docWithoutProof = { ...document, proof: undefined };
  const canonicalDoc = canonicalize(docWithoutProof);
  
  // Create proof config without proofValue
  const proofConfig = { ...proof, proofValue: undefined };
  const canonicalProofConfig = canonicalize(proofConfig);
  
  // Hash the concatenation
  const toVerify = hash(canonicalProofConfig + canonicalDoc);
  
  // Verify signature
  const signature = base58btc.decode(proof.proofValue);
  return verify(publicKey, toVerify, signature);
}

Verification Algorithms

This section defines the core verification algorithms that all conforming verifiers MUST implement. These algorithms are referenced throughout the specification.

Algorithm 11.1: GetEffectiveLeaseState

Purpose: Retrieve the latest valid lease state for a capability from the cache, falling back to initial state if none exists.

Inputs:

  • capabilityId — identifier of the capability
  • capability — the capability credential (for issuanceDate and hash verification)
  • leaseStateCache — cache of received LeaseSyncResponse objects
  • issuerKey — public key for verifying sync responses

Output: The effective lease state (lastSync, previousLastSync, status, capabilityHash)

Steps:

  1. Let candidates = all entries in leaseStateCache for capabilityId.
  2. Sort candidates by newLastSync descending (most recent first).
  3. For each candidate in sorted order:
    1. If verifyProof(candidate, candidate.proof, issuerKey) fails → discard, continue.
    2. If H(canonicalize(capability)) ≠ candidate.capabilityHash → discard, continue.
    3. Return candidate as the effective lease state.
  4. If no valid candidate found, return initial state:
    • newLastSync = capability.issuanceDate
    • previousLastSync = null
    • status = "active"
    • capabilityHash = H(canonicalize(capability))

Algorithm 11.2: VerifyCapability

Purpose: Evaluate a capability's current state and determine whether access should be granted.

Inputs:

  • capability — the capability credential
  • leaseState — result of Algorithm 11.1
  • controllerDid — DID of the controller presenting the capability
  • now — current time (NTP-synchronized wall-clock, milliseconds since epoch)
  • revocationCache — cache of revocation records
  • issuerKey — public key for verifying capability proof

Output: VerificationResult { status, result, reason?, syncEndpoint?, verifierTimestamp? }

Steps:

  1. Check revocation cache: If revocationCache contains capability.id with expiresAt > now:
    → Return { status: "REVOKED", result: "denied", reason: "revoked at " + revokedAt }.
  2. Verify capability proof: If verifyProof(capability, capability.proof, issuerKey) fails:
    → Return { status: "INVALID", result: "denied", reason: "invalid capability proof" }.
  3. Verify controller: If capability.credentialSubject.id ≠ controllerDid:
    → Return { status: "INVALID", result: "denied", reason: "controller mismatch" }.
  4. Check lease state revocation: If leaseState.status == "revoked":
    → Return { status: "REVOKED", result: "denied", reason: "revoked via lease state" }.
  5. Extract parameters:
    • L = leaseState.newLastSync as milliseconds since epoch
    • T = capability.leaseSpec.ttl × 1000
    • G = capability.leaseSpec.gracePeriod × 1000
    • ε = clockTolerance (default 5000 ms)
    • Δ = capability.leaseSpec.futureSkewBound (default 5000 ms)
    • N = now
  6. FUTURE check: If N < L − Δ:
    → Return { status: "FUTURE", result: "denied", reason: "lastSync in future beyond Δ" }.
  7. ACTIVE check: If N ≤ L + T + ε:
    → Return { status: "ACTIVE", result: "granted" }.
  8. STALE check: If N ≤ L + T + G + ε:
    → Return { status: "STALE", result: "sync_required", syncEndpoint: leaseSpec.syncEndpoint, verifierTimestamp: now }.
  9. EXPIRED: Return { status: "EXPIRED", result: "denied", reason: "TTL and grace period elapsed" }.

Algorithm 11.3: ValidateSyncResponse

Purpose: Validate a sync response from the issuer and update the lease state cache if valid.

Inputs:

  • response — the sync response from the issuer
  • capability — the capability credential
  • localLeaseState — the controller's current effective lease state
  • requestNonce — the nonce sent in the corresponding sync request
  • now — current time (milliseconds since epoch)
  • clockTolerance — ε in milliseconds
  • issuerHistory — set of previously issued newLastSync values for this capability
  • issuerKey — public key for verifying response proof

Output: { valid: boolean, leaseState?: LeaseState, revoked?: boolean }

Steps:

  1. If verifyProof(response, response.proof, issuerKey) fails → Return { valid: false, reason: "invalid proof" }.
  2. If response.capabilityId ≠ capability.id → Return { valid: false, reason: "capability ID mismatch" }.
  3. If response.capabilityHash ≠ H(canonicalize(capability)) → Return { valid: false, reason: "capability hash mismatch" }.
  4. Let prevSync = localLeaseState.newLastSync (or issuanceDate if initial state).
    If response.previousLastSync ≠ prevSync:
    → This is a multi-device scenario. Accept if response.previousLastSync is a valid previously-issued newLastSync for this capability (i.e., it appears in issuerHistory or equals issuanceDate). Otherwise return { valid: false, reason: "previousLastSync mismatch" }.
  5. If response.newLastSync ≤ response.previousLastSync → Return { valid: false, reason: "timestamp not strictly increasing" }.
  6. If response.nonce ≠ requestNonce → Return { valid: false, reason: "nonce mismatch" }.
  7. If response.newLastSync as epoch ms > now + clockTolerance → Return { valid: false, reason: "newLastSync in future beyond tolerance" }.
  8. If response.status == "revoked" → Return { valid: true, revoked: true }.
  9. Return { valid: true, leaseState: responseAsLeaseState(response) }.

Algorithm 11.4: VerifyDelegationChain

Purpose: Verify a full delegation chain from root to leaf capability, checking both cryptographic proofs and lease attenuation.

Inputs:

  • chain — ordered list of capabilities, root first
  • leaseStates — map of capabilityId → effective lease state (from Algorithm 11.1)
  • now — current time (ms since epoch)
  • controllerDid — DID of the presenting controller
  • revocationCache — revocation cache
  • maxDepth — maximum allowed delegation depth (default 5)

Output: VerificationResult for the leaf capability

Steps:

  1. If chain.length > maxDepth → Return { status: "INVALID", result: "denied", reason: "delegation chain exceeds max depth" }.
  2. For each capability cap at index i in chain:
    1. Let state = leaseStates[cap.id] or initial state (from Algorithm 11.1).
    2. Let controllerToCheck = chain[i+1].issuer if i < chain.length − 1, else controllerDid.
    3. Let result = VerifyCapability(cap, state, controllerToCheck, now, revocationCache) (Algorithm 11.2).
    4. If result.status ≠ "ACTIVE" → Return result (chain fails at position i).
    5. If i > 0:
      • Verify delegation continuity: The issuer of cap must be authorized by the previous capability. Check that cap.issuer resolves to a DID controlled by chain[i−1].credentialSubject.id (or is exactly that DID).
      • Verify lease attenuation: cap.leaseSpec.ttl + cap.leaseSpec.gracePeriod ≤ chain[i−1].leaseSpec.ttl + chain[i−1].leaseSpec.gracePeriod.
      • If either check fails → Return { status: "INVALID", result: "denied", reason: "delegation chain integrity violation at position " + i }.
  3. Return { status: "ACTIVE", result: "granted" }.

DIDComm Integration

Lease-CAP can be integrated with DIDComm for agent-to-agent communication. This section provides guidance on message wrapping, asynchronous handling, and mediator support.

Message Wrapping

Lease-CAP sync messages SHOULD be wrapped in DIDComm envelopes for agent-to-agent communication:

{
  "id": "1234567890",
  "typ": "application/didcomm-plain+json",
  "type": "https://didcomm.org/lease-cap/1.0/sync",
  "from": "did:key:controller",
  "to": "did:key:issuer",
  "created_time": 1705312800,
  "expires_time": 1705316400,
  "body": {
    "type": "LeaseSyncRequest",
    "capabilityId": "urn:cap:9f8e7d6c...",
    "lastKnownSync": "2024-01-15T10:00:00Z",
    "nonce": "4b3a2c1d-8e7f-6a5b"
  },
  "attachments": [
    { "id": "proof-1", "data": { ... } }
  ]
}

Asynchronous Sync Handling

In high-latency or asynchronous DIDComm environments:

  1. Controllers SHOULD initiate sync proactively before TTL expiration to account for message delivery latency.
  2. Verifiers SHOULD accept sync responses with created timestamps up to clockTolerance after the request was queued.
  3. Issuers SHOULD support async response queues for offline controllers.

DIDComm-Specific Timing Parameters:

ParameterValueDescription
AsyncGraceMultiplier 1.5 Extended grace for async response delivery (applies to message latency, not capability grace)
MaxAsyncLatency 30000 ms Maximum expected round-trip time for DIDComm messages
RetryBackoffBase 2000 ms Base delay for async retries

Important Distinction: AsyncGraceMultiplier applies to the delivery latency of the DIDComm message itself, not to the capability grace period. It is distinct from offlineMode.graceMultiplier, which governs how long a verifier tolerates issuer unreachability.

Mediator Support

  1. Sync requests MAY be sent through DIDComm mediators.
  2. Mediators MUST NOT modify lease state, proofs, or timestamps.
  3. Verifiers MUST verify that sync responses carry a valid proof from the issuer's DID, not from the mediator.
  4. Controllers SHOULD use end-to-end encryption when routing through mediators.

Compatibility with Existing Systems

Lease-CAP is designed as a temporal control layer that can be applied to existing capability-based authorization systems without modification to their core models.

Wrapping UCAN

Lease-CAP can be applied to UCAN delegation chains by adding a lease layer:

{
  "ucan": { ... },  // Original UCAN token
  "lease": {
    "capabilityId": "urn:cap:...",
    "syncEndpoint": "https://issuer.example.com/sync",
    "leaseSpec": { 
      "ttl": 86400, 
      "gracePeriod": 300 
    },
    "lastSync": "2024-01-15T10:00:00Z"  // From sync response
  }
}

The lease layer adds liveness guarantees to UCAN's delegation model. The UCAN's exp field SHOULD be set to issuanceDate + maxLifetime and treated as an absolute upper bound, while lease state provides fine-grained liveness control.

Extending ZCAP-LD

ZCAP-LD root capabilities can be enhanced with a lease specification:

{
  "@context": [
    "https://w3id.org/zcap/v1", 
    "https://w3id.org/lease-cap/v1"
  ],
  "id": "urn:zcap:root:...",
  "controller": "did:key:...",
  "invocationTarget": "...",
  "leaseSpec": {
    "ttl": 86400,
    "gracePeriod": 300,
    "syncEndpoint": "https://issuer.example.com/sync"
  }
}

Implementation Guidance

This section provides practical guidance for implementers, including parameter selection, sync strategies, and error handling.

Parameter Selection Guide

The following table provides recommended parameter values for different use cases:

Use Case TTL GracePeriod SyncLeadTime Jitter MaxLifetime OfflineMode Δ
High-frequency API 3600 s (1 hour) 60 s 0.8 10% 7 days Disabled 5 s
Web application 86400 s (24 hours) 300 s (5 minutes) 0.8 10% 30 days Optional 5 s
Financial transactions 7200 s (2 hours) 120 s 0.7 5% 14 days Disabled 1 s
IoT devices 604800 s (7 days) 3600 s (1 hour) 0.7 15% 90 days Enabled (1.5×) 30 s
Satellite/cross-border 86400 s (24 hours) 600 s (10 minutes) 0.6 10% 60 days Enabled (1.5×) 30 s

Parameter rationale:

Sync Strategies

StrategyDescriptionLatencyPrivacyRecommended For
Proactive Sync before TTL expires Zero (no sync delay on access) Medium (regular sync patterns) Most use cases
On-demand Sync on STALE response from verifier First request delayed High (sync only when needed) High-latency environments, privacy-sensitive
Hybrid Proactive with on-demand fallback Near-zero Medium Production systems
Batch Multiple capabilities synced together Zero (if scheduled proactively) High (reduces correlation) Many capabilities per controller

Error Reference

CodeRetryableCauseController Action
INVALID_PROOFNoSignature verification failedCheck key material; re-issue capability
CAPABILITY_NOT_FOUNDNoUnknown capabilityIdVerify capability ID; request re-issuance
CAPABILITY_REVOKEDNoIssuer has revoked this capabilityRequest new capability
CAPABILITY_HASH_MISMATCHNoLease state does not match presented credentialObtain correct sync response for this capability
RATE_LIMITEDYes (after retryAfter)Too many sync requestsWait for retryAfter seconds; implement backoff
SYNC_REQUIREDYes (after sync)Capability is STALEPerform sync and retry
EXPIREDNoTTL + GracePeriod elapsedRequest re-issuance
FUTURE_TIMESTAMPNolastSync is beyond futureSkewBoundCheck system clock; obtain correct sync response
PARENT_NOT_ACTIVENoParent capability is STALE or EXPIREDSync parent first, then child

Audit Logging

Verifiers SHOULD log the following for each verification event:

interface AuditEvent {
  timestamp: string;           // ISO 8601 timestamp
  capabilityId: string;        // Capability identifier
  capabilityHash: string;      // Hash of capability credential
  controllerDid: string;       // Controller DID
  action: 'invoke' | 'sync' | 'revoke' | 'expire' | 'offline_grant';
  result: 'granted' | 'denied' | 'sync_required' | 'granted_offline';
  stateAtTime: 'ACTIVE' | 'STALE' | 'EXPIRED' | 'REVOKED' | 'FUTURE' | 'INVALID';
  syncLatencyMs?: number;      // For sync operations
  offlineRemainingSeconds?: number;  // For offline grants
  clockDriftMs?: number;       // Detected clock drift
  reason?: string;             // For denied results
}

Jitter Calculation

function calculateSyncDelay(
  ttl: number,                    // TTL in seconds
  syncLeadTime: number = 0.8,     // Lead time fraction (0-1)
  jitterPercent: number = 0.1     // Jitter as fraction of base delay
): number {
  const baseDelay = ttl * syncLeadTime;
  const jitter = baseDelay * jitterPercent;
  const randomJitter = (Math.random() * 2 - 1) * jitter;
  return Math.max(0, baseDelay + randomJitter);
}

Example: TTL=86400 (24h), syncLeadTime=0.8 → baseDelay=69120s (19.2h). With jitterPercent=0.1, jitter range = ±6912s (±1.92h). Actual sync time = 19.2h ± up to 1.92h.

Randomized Truncated Exponential Backoff

async function syncWithBackoff(
  syncFn: () => Promise<void>,
  maxAttempts: number = 5,
  baseDelay: number = 1000,    // milliseconds
  maxDelay: number = 60000      // milliseconds
): Promise<void> {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      return await syncFn();
    } catch (err) {
      if (attempt === maxAttempts - 1) throw err;
      
      const exponentialDelay = baseDelay * Math.pow(2, attempt);
      const jitter = exponentialDelay * 0.1 * Math.random();
      const backoffDelay = Math.min(maxDelay, exponentialDelay + jitter);
      
      await sleep(backoffDelay);
    }
  }
}

Expected delays (baseDelay=1000ms, maxDelay=60000ms):

Security Considerations

This section provides detailed analysis of security threats and their mitigations. Implementers MUST consider these threats when deploying Lease-CAP in production environments.

Clock Skew and Synchronization

Threat: Malicious or misconfigured verifiers use skewed clocks to prematurely expire capabilities or improperly extend STALE windows. An attacker with local admin rights rolls back the system clock to revive an EXPIRED capability.

Mitigation:

  1. Verifiers MUST apply clock tolerance ε (RECOMMENDED: 5000 ms) symmetrically to the ACTIVE/STALE and STALE/EXPIRED boundaries.
  2. Verifiers MUST enforce future skew bound Δ, configurable per leaseSpec.
  3. High-security verifiers MUST use NTP-synchronized clocks with hardware-backed time sources (e.g., TPM, Secure Enclave).
  4. Verifiers SHOULD detect clock tampering by comparing NTP-synchronized time vs. local monotonic time; if divergence exceeds , the verifier SHOULD raise an alert and MAY suspend verification.
  5. Verifiers MUST include their current timestamp in 403 Sync Required responses, enabling controllers to detect drift.

Replay Attacks

Threat: Attacker replays old sync responses to extend capability lifetime beyond intended bounds.

Mitigation:

  1. Sync responses MUST include strictly increasing newLastSync timestamps.
  2. Sync responses MUST include previousLastSync matching the controller's current state (subject to multi-device allowance).
  3. Sync responses MUST include a nonce matching the request nonce.
  4. Verifiers and controllers MUST reject any response where newLastSync ≤ previousLastSync.
  5. Used nonces SHOULD be stored for TTL + GracePeriod to detect replay within a single session.
  6. Issuers MUST remember previously issued newLastSync values for at least TTL + GracePeriod.

Revocation Race Condition

Threat: A capability is revoked, but the controller syncs successfully before the verifier learns of the revocation, receiving a valid-looking sync response.

Mitigation:

  1. Issuers MUST NOT issue a successful LeaseSyncResponse for a revoked capability.
  2. Issuers MUST return status: "revoked" in sync responses for revoked capabilities.
  3. Verifiers MUST cache revocation proofs with expiresAt = max(revokedAt + TTL + GracePeriod, lastSeenTimestamp + TTL + GracePeriod).
  4. The maximum window for a revoked capability to remain usable is bounded by TTL + GracePeriod.

Offline Mode Security

Threat: Offline mode is abused to extend capability lifetime far beyond the intended window, or an attacker induces artificial network partitions to keep capabilities alive.

Mitigation:

  1. Offline mode MUST be explicitly enabled by the issuer in leaseSpec.offlineMode.enabled.
  2. Verifiers MUST enforce maxDurationSeconds as a hard upper bound on total offline operation.
  3. graceMultiplier MUST NOT exceed 2.0; issuers SHOULD use the lowest effective value.
  4. Offline grants MUST produce audit log entries with an offline_grant action and remaining duration.
  5. High-security deployments SHOULD set offlineMode.enabled: false.

Formal offline expiry:

offlineExpiry = min(
  lastSync + TTL + (GracePeriod × graceMultiplier),
  lastSync + maxDurationSeconds
)

Denial of Service

Threat: Attackers flood sync endpoints with requests to overwhelm issuers.

Mitigation: Issuers MUST implement rate limiting. Controllers MUST implement truncated exponential backoff with jitter.

Key Compromise

Threat: An attacker steals a controller's private key and syncs capabilities indefinitely.

Mitigation: The issuer can revoke the capability. The maximum window of abuse is bounded by T + G from the last legitimate sync before revocation.

Thundering Herd

Threat: Many devices attempt to sync simultaneously, overwhelming the issuer.

Mitigation: Controllers MUST apply jitter to sync scheduling. Issuers MAY provide a nextSyncRecommended field to distribute load.

Privacy Considerations

This section analyzes privacy implications of Lease-CAP and provides mitigation strategies.

Correlation Risk

Each sync request reveals to the issuer that the controller is active and exercising a particular capability at a particular time. Over time, this creates a behavioral profile that could be used to track the controller.

Mitigation:

  1. Implement batch sync to reduce request frequency.
  2. Use nextSyncRecommended to avoid predictable sync timing.
  3. Deployments MAY use BBS+ signatures for unlinkable proofs in high-privacy contexts.
  4. Issuers SHOULD NOT log sync request timestamps at granularity finer than the TTL interval.

Batch Sync

Controllers SHOULD use batch sync when managing multiple capabilities from the same issuer to reduce correlation:

{
  "type": "BatchLeaseSyncRequest",
  "capabilities": [
    { 
      "capabilityId": "urn:cap:abc123", 
      "lastKnownSync": "2024-01-15T10:00:00Z", 
      "nonce": "aaa-111" 
    },
    { 
      "capabilityId": "urn:cap:def456", 
      "lastKnownSync": "2024-01-15T10:00:00Z", 
      "nonce": "bbb-222" 
    }
  ],
  "proof": { ... }  // Single proof covering the entire batch request
}

The issuer MUST return individual LeaseSyncResponse objects for each capability within the batch. Each response carries its own capabilityHash, nonce, and proof. Batch sync verification follows Algorithm 11.3 applied independently to each response in the batch.

Data Minimization

Sync requests MUST NOT contain unnecessary identifying information beyond what is required by Algorithm 11.3. Implementations SHOULD strip request metadata (User-Agent, Referer headers) from sync HTTP requests.

Test Vectors

Each test vector specifies the full inputs (capability, lease state cache, reference time, controller DID) and the expected output. Implementations MUST pass all vectors to claim conformance.

TV-01: ACTIVE State

{
  "name": "TV-01: ACTIVE — should grant access",
  "capability": {
    "id": "urn:cap:tv-01",
    "issuanceDate": "2024-01-15T10:00:00Z",
    "credentialSubject": {
      "id": "did:key:controller-tv01",
      "capability": { "leaseSpec": { "ttl": 86400, "gracePeriod": 300 } }
    }
  },
  "leaseStateCache": {
    "urn:cap:tv-01": {
      "newLastSync": "2024-01-15T10:00:00Z",
      "capabilityHash": "H(capability)",
      "status": "active"
    }
  },
  "controllerDid": "did:key:controller-tv01",
  "now": "2024-01-15T15:00:00Z",
  "expected": { "status": "ACTIVE", "result": "granted" }
}

Note: lastSync is held in the leaseStateCache entry, not in credentialSubject. This is the correct structure for all test vectors.

TV-02: STALE State

{
  "name": "TV-02: STALE — TTL elapsed, within grace, should require sync",
  "leaseStateCache": {
    "urn:cap:tv-02": {
      "newLastSync": "2024-01-15T10:00:00Z",
      "status": "active"
    }
  },
  "now": "2024-01-16T10:02:00Z",
  "comment": "now = lastSync + 24h + 2min; TTL=86400s expired, GracePeriod=300s not yet elapsed",
  "expected": { "status": "STALE", "result": "sync_required" }
}

TV-03: EXPIRED State

{
  "name": "TV-03: EXPIRED — TTL + GracePeriod elapsed, should deny",
  "leaseStateCache": {
    "urn:cap:tv-03": {
      "newLastSync": "2024-01-15T10:00:00Z",
      "status": "active"
    }
  },
  "now": "2024-01-16T10:10:00Z",
  "comment": "now = lastSync + 24h + 10min; TTL=86400s, GracePeriod=300s, both elapsed (with ε=5s)",
  "expected": { "status": "EXPIRED", "result": "denied" }
}

TV-04: FUTURE State

{
  "name": "TV-04: FUTURE — lastSync set far in future, should deny",
  "leaseStateCache": {
    "urn:cap:tv-04": {
      "newLastSync": "2030-01-15T10:00:00Z",
      "status": "active"
    }
  },
  "now": "2024-01-15T15:00:00Z",
  "comment": "now < newLastSync − Δ; Δ=5000ms",
  "expected": { "status": "FUTURE", "result": "denied" }
}

TV-05: No Prior Sync (Initial State)

{
  "name": "TV-05: No prior LeaseSyncResponse — initial state uses issuanceDate",
  "capability": {
    "id": "urn:cap:tv-05",
    "issuanceDate": "2024-01-15T10:00:00Z",
    "credentialSubject": {
      "id": "did:key:controller-tv05",
      "capability": { "leaseSpec": { "ttl": 86400, "gracePeriod": 300 } }
    }
  },
  "leaseStateCache": {},
  "now": "2024-01-15T12:00:00Z",
  "comment": "issuanceDate used as lastSync; now is 2h after issuance, well within TTL=24h",
  "expected": { "status": "ACTIVE", "result": "granted" }
}

Acknowledgments

This specification builds on work from:

Special thanks to reviewers who identified critical gaps in state separation, cryptographic binding, revocation race conditions, multi-device sync handling, offline mode constraints, and DIDComm integration requirements.

Appendix A: Comparison with UCAN and ZCAP-LD

FeatureUCANZCAP-LDLease-CAP
DelegationYes (hash chains)Yes (proof chains)Yes (with attenuation rule)
ExpiryOptionalNoMandatory
RevocationNoNoYes (bounded by T+G)
Liveness guaranteeNoNoYes
Offline operationYes (unbounded)Yes (unbounded)Yes (bounded, opt-in)
Temporal freshnessNoNoYes
State separation (credential vs. lease)NoNoYes
Cryptographic binding (hash)NoNoYes
Multi-device syncImplicitImplicitExplicitly specified

Appendix B: JSON-LD Context

{
  "@context": {
    "@version": 1.1,
    "@protected": true,
    "LeaseCapability": "https://w3id.org/lease-cap#LeaseCapability",
    "LeaseSyncRequest": "https://w3id.org/lease-cap#LeaseSyncRequest",
    "LeaseSyncResponse": "https://w3id.org/lease-cap#LeaseSyncResponse",
    "capabilityId": "https://w3id.org/lease-cap#capabilityId",
    "capabilityHash": "https://w3id.org/lease-cap#capabilityHash",
    "previousLastSync": "https://w3id.org/lease-cap#previousLastSync",
    "newLastSync": "https://w3id.org/lease-cap#newLastSync",
    "nextSyncRecommended": "https://w3id.org/lease-cap#nextSyncRecommended",
    "ttl": "https://w3id.org/lease-cap#ttl",
    "gracePeriod": "https://w3id.org/lease-cap#gracePeriod",
    "futureSkewBound": "https://w3id.org/lease-cap#futureSkewBound",
    "offlineMode": "https://w3id.org/lease-cap#offlineMode",
    "maxDurationSeconds": "https://w3id.org/lease-cap#maxDurationSeconds",
    "graceMultiplier": "https://w3id.org/lease-cap#graceMultiplier",
    "verifierTimestamp": "https://w3id.org/lease-cap#verifierTimestamp",
    "syncEndpoint": "https://w3id.org/lease-cap#syncEndpoint",
    "syncMethod": "https://w3id.org/lease-cap#syncMethod",
    "VerifiableCredential": "https://www.w3.org/2018/credentials#VerifiableCredential",
    "DataIntegrityProof": "https://w3id.org/security#DataIntegrityProof"
  }
}

Note: lastSync is intentionally absent from this context. It is not a field in any conforming Lease-CAP document; newLastSync in LeaseSyncResponse objects is the canonical form.

Appendix C: Summary of Changes from Version 1.0

SectionChange
§3.2 State DefinitionsState boundaries made strictly non-overlapping; ε is a tolerance, not a state region
§6.1 State SeparationStrengthened prohibition on embedding lastSync in VC; rationale added
§7.1 Capability CredentialRemoved lastSync from capability credential data model
§7.7 Revocation CacheexpiresAt formula corrected to max(revokedAt + TTL + GracePeriod, lastSeenTimestamp + TTL + GracePeriod)
§7.8 Offline ModeOffline mode configuration (offlineMode object) added to leaseSpec data model
§9.5 Multi-Device SyncMulti-device sync flow added
§11.3 Algorithm 11.3Step 4 now specifies multi-device handling explicitly
§11.4 Algorithm 11.4New Algorithm 11.4: VerifyDelegationChain (full chain)
§15.10 Offline Mode SecurityOffline mode security formalized with explicit maxDurationSeconds enforcement
§19 Test VectorsTest vectors corrected: lastSync in cache only, not in credential body

Appendix D: Glossary

ACTIVE State
The capability is within its TTL window and can be used without issuer contact.
Capability
A verifiable assertion of authorization to perform specific actions on a specific resource.
Capability Hash
A SHA-256 hash of the canonicalized capability credential, used to bind lease state to a specific credential.
Controller
The entity that holds and exercises a capability.
Delegation Chain
A sequence of capabilities where each capability is delegated from the previous one.
Effective Lease State
The current validity state derived from the latest valid LeaseSyncResponse.
EXPIRED State
The capability has exceeded both TTL and GracePeriod; it cannot be used or renewed.
FUTURE State
The lastSync timestamp is too far in the future relative to the verifier's clock.
GracePeriod
The duration after TTL expiration during which sync can still succeed.
Issuer
The entity that creates and signs capability credentials and sync responses.
LastSync
The timestamp of the most recent successful synchronization, from a LeaseSyncResponse.
Lease
The temporal validity period defined by TTL and GracePeriod.
Lease Attenuation
The principle that delegated capabilities cannot outlive their parent's absolute expiration.
Liveness-Bound Capability (LBC)
A capability whose validity requires periodic synchronization with the issuer.
Liveness Guarantee
The property that a capability remains valid only while the controller successfully synchronizes within bounded intervals.
Offline Mode
An optional mode where verifiers grant access even when the issuer is unreachable, within bounded limits.
REVOKED State
The issuer has explicitly revoked the capability; it cannot be used.
STALE State
The TTL has expired but the capability is within the grace period; sync is required before access.
Sync
The process of the controller contacting the issuer to update the lease state.
TTL (Time-To-Live)
The duration that a capability remains ACTIVE after lastSync.
Verifier
The entity that checks capability validity before granting resource access.