Tenuo Protocol Specification
Version: 1.0
Status: Normative
Date: 2026-01-10
Authors: Niki Aimable Niyikiza
Related Documents:
- wire-format-v1.md - Wire Format Specification (CBOR encoding, field IDs)
- test-vectors.md - Byte-exact test vectors for validation
Abstract
Tenuo is a capability-based authorization protocol for AI agent systems. It enables secure delegation of authority through cryptographically signed tokens called warrants, ensuring that compromised or misbehaving agents cannot exceed their granted permissions. Warrants support attenuation (authority can only shrink, never expand) and proof-of-possession (stolen tokens are useless without the holder’s private key).
Normative Precedence: In case of conflict between this document and wire-format-v1.md, the wire format specification is authoritative for encoding details.
Terminology
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].
| Term | Definition |
|---|---|
| Warrant | A signed capability token granting specific permissions to a holder |
| Issuer | The entity whose signature authorizes a warrant |
| Holder | The entity authorized to use a warrant (bound via public key) |
| Verifier | The entity validating the warrant chain and PoP (e.g., the Authorizer) |
| Attenuation | The process of creating a child warrant with reduced authority |
| Delegation | Granting authority to another entity via attenuation |
| Proof-of-Possession (PoP) | Cryptographic proof that the presenter controls the holder’s private key |
| Constraint | A restriction on argument values (e.g., Pattern("/data/*.pdf")) |
| Clearance | A monotonic trust level (0-255) used to enforce provenance requirements |
| Terminal | A warrant state where depth >= max_depth, prohibiting further delegation |
| Envelope | The outer CBOR structure containing the payload and signature |
| WarrantStack | An ordered array of warrants from root to leaf for chain verification |
| Control Plane | The trusted root authority that issues initial warrants |
| Trusted Root | A public key the verifier trusts as a chain anchor |
1. Threat Model
1.1 Protected Threats
| Threat | Mitigation |
|---|---|
| Prompt injection | Warrants constrain what tools/args are allowed regardless of agent intent |
| Stolen warrant | Proof-of-Possession requires holder’s private key |
| Privilege escalation | Attenuation ensures child ⊆ parent authority |
| Confused deputy | PoP binds (warrant, tool, args) tuple to specific invocation |
| Replay attack | Time-windowed PoP signatures (~2 min validity) |
| Chain forgery | Cryptographic signatures at every delegation link |
| Unbounded delegation | MAX_DELEGATION_DEPTH (64) enforced |
1.2 Out of Scope
| Threat | Reason |
|---|---|
| Key compromise | Tenuo assumes holder keys are secure; revocation is optional |
| Denial of service | Rate limiting is application-layer concern |
| Side channels | Tenuo is a protocol, not an implementation |
| Malicious Control Plane | Root of trust; if compromised, all warrants are suspect |
1.3 Trust Assumptions
- The Control Plane is trusted and issues only valid warrants
- Cryptographic primitives (Ed25519, SHA-256) are secure
- Clocks are synchronized within ±30 seconds
- Private keys are protected by their holders
1.4 Invariants Summary
The protocol enforces six invariants during chain verification and authorization:
| ID | Name | Rule |
|---|---|---|
| I1 | Delegation Authority | child.issuer == parent.holder |
| I2 | Depth Monotonicity | child.depth == parent.depth + 1 |
| I3 | TTL Monotonicity | child.expires_at ≤ parent.expires_at |
| I4 | Capability Monotonicity | child.capabilities ⊆ parent.capabilities |
| I5 | Cryptographic Linkage | child.parent_hash == SHA256(parent.payload_bytes) |
| I6 | Proof-of-Possession | PoP signature verifies under warrant.holder |
2. Warrant Model
2.1 Structure
A warrant is a signed envelope containing a payload:
SignedWarrant {
envelope_version: u8, // Currently 1
payload: bytes, // CBOR-encoded WarrantPayload
signature: Signature, // Over b"tenuo-warrant-v1" || envelope_version || payload
}
2.2 Warrant Types
| Type | Purpose | Use Case |
|---|---|---|
| Execution | Invoke specific tools with constraints | Worker agents |
| Issuer | Grant execution warrants to others | Orchestrators, planners |
2.3 Fields
2.3.1 Common Fields
| Field | Type | Required | Description |
|---|---|---|---|
version |
u8 | Yes | Payload version (currently 1) |
id |
bytes[16] | Yes | UUIDv7 warrant identifier |
warrant_type |
u8 | Yes | 0 = Execution, 1 = Issuer |
holder |
PublicKey | Yes | Authorized user of this warrant |
issuer |
PublicKey | Yes | Signer of this warrant |
issued_at |
u64 | Yes | Unix timestamp (seconds) |
expires_at |
u64 | Yes | Unix timestamp (seconds) |
depth |
u32 | Yes | Current delegation depth (0 = root) |
max_depth |
u8 | Yes | Maximum allowed depth (ceiling) |
parent_hash |
bytes[32] | No | SHA-256 of parent’s payload_bytes (null for root) |
clearance |
u8 | No | Privilege level (higher = more access) |
required_approvers |
array<PublicKey> | No | Keys that must approve execution |
min_approvals |
u32 | No | Threshold (default: all required_approvers) |
extensions |
map | No | Application-specific metadata |
Note: Approvals are single-layer signatures. Unlike warrants, approvers cannot delegate their approval authority to sub-approvers. Each approval MUST be signed directly by a key in
required_approvers.
Note:
depthis semantically bounded to[0, 64]regardless of wire width. Verifiers MUST reject warrants withdepth > MAX_DELEGATION_DEPTH (64).
2.3.2 Execution Warrant Fields
| Field | Type | Description |
|---|---|---|
tools |
map<string, ConstraintSet> | Tool name to argument constraints |
2.3.3 Issuer Warrant Fields
| Field | Type | Description |
|---|---|---|
issuable_tools |
array<string> | Tools this issuer can grant |
max_issue_depth |
u8 | Max depth for issued warrants |
constraint_bounds |
ConstraintSet | Limits on issued constraints |
Note:
max_depthandmax_issue_depthareu8because MAX_DELEGATION_DEPTH is 64. Thedepthfield isu32for wire compatibility but is semantically bounded to [0, 64]. Verifiers MUST reject warrants withdepth > 64.
2.4 Issuer Warrant Rules
- Issuer warrants MUST have an empty
toolsmap constraint_boundslimits what constraints can be granted to issued warrantsmax_issue_depthlimits themax_depthof issued warrants (not theirdepth)- Issuer warrants can be attenuated to other issuer warrants (same rules apply)
Example: An issuer warrant with:
"constraint_bounds": {"path": [2, {"pattern": "/data/*"}]}
can only issue execution warrants where path is narrower than /data/*:
Pattern("/data/reports/*")- valid (subset of/data/*)Exact("/data/q3.pdf")- valid (single file within/data/)Pattern("/logs/*")- REJECTED (not within bounds)Wildcard- REJECTED (broader than bounds)
2.5 Example
{
"id": "01942b4e-7dec-7123-a765-00a0c91e6bf6",
"warrant_type": 0,
"tools": {
"read_file": {"path": [1, "/data/q3.pdf"]}
},
"holder": {"algorithm": 1, "bytes": "abc..."},
"issuer": {"algorithm": 1, "bytes": "xyz..."},
"issued_at": 1736400000,
"expires_at": 1736500000,
"depth": 1,
"max_depth": 3
}
3. Constraints
3.1 Constraint Types
| Type | ID | Wire Value | Description | Example |
|---|---|---|---|---|
| Exact | 1 | {value: any} |
Exact value match | Exact("/data/report.pdf") |
| Pattern | 2 | {pattern: string} |
Glob pattern | Pattern("/data/*.pdf") |
| Range | 3 | {min?, max?: f64} |
Numeric bounds | Range(min=0, max=1000) |
| OneOf | 4 | {values: [any]} |
Set membership | OneOf(["dev", "staging"]) |
| Regex | 5 | {pattern: string} |
Regular expression | Regex("^[a-z]+\\.pdf$") |
| Reserved | 6 | - | Future IntRange (i64) | - |
| NotOneOf | 7 | {excluded: [any]} |
Set exclusion | NotOneOf(["prod"]) |
| Cidr | 8 | {network: string} |
IP range | Cidr("10.0.0.0/8") |
| UrlPattern | 9 | {pattern: string} |
URL matching | UrlPattern("https://*.example.com/*") |
| Contains | 10 | {required: [any]} |
List contains values | Contains(["admin"]) |
| Subset | 11 | {allowed: [any]} |
Array subset | Subset(["read", "write"]) |
| All | 12 | {constraints: [C]} |
Logical AND | All([...]) |
| Any | 13 | {constraints: [C]} |
Logical OR | Any([...]) |
| Not | 14 | {constraint: C} |
Logical NOT | Not(Pattern("*.exe")) |
| Cel | 15 | {expr: string} |
CEL expression | Cel("size < 1000000") |
| Wildcard | 16 | null |
Any value | Wildcard() |
Security Note (CEL): CEL expressions execute in a sandbox with limited functions. Implementations SHOULD enforce execution timeouts (recommended: 10ms) and memory limits to prevent resource exhaustion attacks.
Security Note (Regex): Regex patterns are vulnerable to ReDoS (catastrophic backtracking). Implementations SHOULD use bounded regex engines with backtrack limits or enforce execution timeouts.
3.2 Constraint Lattice
Constraints form a partial order. Attenuation moves toward more restrictive types:
Wildcard (⊤)
│
┌────────────────────┼────────────────────┐
│ │ │
Pattern Range OneOf
│ │ │
└────────────────────┼────────────────────┘
│
Exact (⊥)
Validity rule: A child constraint C_child is valid iff for all v: C_child(v) implies C_parent(v)
3.3 Serialization
Constraints are CBOR arrays: [type_id, value]
The value structure varies by type. For types with a single field, the value is inlined directly. For types with multiple fields, a map is used:
Exact(1): [1, "/data/q3.pdf"] // Inlined value
Pattern(2): [2, {"pattern": "/data/*.pdf"}]
Range(3): [3, {"min": 0, "max": 1000}]
OneOf(4): [4, {"values": ["dev", "staging"]}]
Wildcard(16): [16, null] // No value needed
3.4 Unknown Handling
| Location | Unknown Data | Behavior |
|---|---|---|
| Payload fields | Unknown CBOR keys | REJECT |
| Constraint type IDs | Unrecognized ID | Deserialize to Unknown, FAIL at authorization |
Extension keys (tenuo.*) |
Reserved namespace | REJECT (fail closed) |
| Extension keys (user-defined) | Any other key | PRESERVE (passthrough) |
If a constraint wire value is not of the expected CBOR type, it MUST be rejected.
Unknown constraints participate in attenuation checks as opaque values and MUST NOT be removed or weakened.
3.5 Constraint Type Registry
| Range | Allocation |
|---|---|
| 1-16 | Core (this spec) |
| 17-127 | Reserved (future specs) |
| 128-255 | Experimental (fail closed) |
4. Attenuation
4.1 Delegation Authority (Invariant I1)
child.issuer == parent.holder
The parent’s holder is the only entity authorized to delegate. This establishes an unambiguous audit trail.
4.2 Monotonicity Invariant (I4)
Every dimension MUST satisfy: child ⊆ parent
| Dimension | Rule | Violation |
|---|---|---|
| Capabilities | child_caps ⊆ parent_caps | Cannot add tools or widen constraints |
| TTL | child_expires ≤ parent_expires | Cannot extend |
| Depth | child_depth == parent_depth + 1 | Must increment exactly |
| Clearance | child_clearance ≤ parent_clearance | Cannot escalate privilege |
Clearance semantics:
- Single global scalar per warrant (not tool-scoped)
- Default value is 0 if absent
- Higher numbers mean more privilege
- Tool requirements compare against it monotonically
4.3 Depth Limits (Invariant I2)
child.depth == parent.depth + 1
child.depth <= MAX_DELEGATION_DEPTH (64)
child.depth <= parent.max_depth
Note: max_depth is an absolute ceiling, not a remaining count. A warrant is terminal when depth >= max_depth.
4.4 TTL Monotonicity (Invariant I3)
child.expires_at <= parent.expires_at
child.ttl <= MAX_WARRANT_TTL_SECS (90 days)
4.5 Required Narrowing
Every delegation SHOULD narrow at least one dimension:
toolsset is a strict subset, OR- Any constraint becomes strictly stronger, OR
expires_atdecreases, ORmax_depthdecreases
This is guidance, not a verifier invariant.
4.6 Cryptographic Linkage (Invariant I5)
child.parent_hash == SHA256(parent.payload_bytes)
Every child warrant is cryptographically bound to its parent via hash of the parent’s payload. This prevents chain splicing attacks.
5. Chain Verification
5.1 WarrantStack Model
Delegation chains are verified using a WarrantStack - an ordered array from root to leaf:
WarrantStack = [SignedWarrant, SignedWarrant, ...] // Root -> Leaf
5.2 Verification Algorithm
Critical: Verify-before-deserialize. For every SignedWarrant:
- Extract (envelope_version, payload_bytes, signature)
- Verify signature against raw payload_bytes BEFORE deserializing
- Only after verification, parse payload fields
def verify_chain(stack: list[Warrant], trusted_roots: set[PublicKey]) -> bool:
if not stack:
raise ChainVerificationFailed("Empty stack")
# Root must be from trusted issuer
if stack[0].issuer() not in trusted_roots:
raise ChainNotAnchored("Root issuer not trusted")
# Verify each link
for i in range(1, len(stack)):
parent, child = stack[i-1], stack[i]
# I1: Delegation authority
assert child.issuer() == parent.holder()
# I2: Depth monotonicity
assert child.depth() == parent.depth() + 1
assert child.depth() <= parent.max_depth()
# I3: TTL monotonicity
assert child.expires_at() <= parent.expires_at()
# I4: Capability attenuation
assert child.capabilities() <= parent.capabilities()
# I5: Cryptographic linkage
assert child.parent_hash() == SHA256(parent.payload_bytes())
# Signature verification (with domain separation)
preimage = b"tenuo-warrant-v1" + bytes([child.envelope_version()]) + child.payload_bytes()
verify(child.issuer(), preimage, child.signature())
return True
5.3 Trust Anchors
Root warrants are NOT required to be self-signed. Trust is established by:
if chain[0].issuer() not in verifier.trusted_roots:
raise ChainNotAnchored("root issuer not trusted")
| Question | Answer |
|---|---|
| What makes a root trusted? | Root’s issuer is in verifier’s trusted_roots |
| Must issuer == holder? | No. Self-signing is common but not required |
| How to rotate? | Add new key to trusted_roots, issue new warrants, remove old |
5.4 Cycle Protection
| Pattern | Status |
|---|---|
| Same warrant ID twice | BLOCKED (cycle detection) |
| Holder A->B->A (different warrants) | ALLOWED (monotonicity makes it safe) |
| Self-issuance | BLOCKED (privilege escalation) |
6. Authorization Logic
Authorization applies a verified chain to a specific request:
def authorize(warrant, tool, args, pop_signature, approvals=[], tool_reqs={}):
"""Check if a request is authorized by the warrant.
Args:
tool_reqs: Maps tool names to required clearance level (u8).
Default requirement is 0 if not specified.
Precondition: warrant's chain has already passed verify_chain().
"""
# 1. Check tool is allowed
if tool not in warrant.tools():
raise ToolNotAllowed(tool)
# 2. Check clearance meets tool requirements
required_clearance = tool_reqs.get(tool, 0)
if warrant.clearance() < required_clearance:
raise InsufficientClearance(f"Have {warrant.clearance()}, need {required_clearance}")
# 3. Check constraints are satisfied
for arg_name, arg_value in args.items():
constraint = warrant.constraint_for(tool, arg_name)
if not constraint.satisfied_by(arg_value):
raise ConstraintNotSatisfied(arg_name, arg_value, constraint)
# 4. Check warrant not expired
if now() > warrant.expires_at():
raise WarrantExpired()
# 5. Verify Proof-of-Possession (§7)
verify_pop(warrant, pop_signature, tool, args)
# 6. Multi-sig enforcement (if required)
if warrant.required_approvers():
valid = count_valid_approvals(warrant, tool, args, approvals)
if valid < warrant.min_approvals():
raise InsufficientApprovals(f"Got {valid}, need {warrant.min_approvals()}")
return True
Key distinction:
- Chain verification (§5): Validates signatures, invariants, linkage - no request context
- Authorization (§6): Applies warrant to (tool, args, approvals) - requires request context
7. Proof-of-Possession
7.1 Challenge Structure
PopChallenge = (warrant_id, tool, sorted_args, timestamp_window)
Preimage = b"tenuo-pop-v1" || CBOR(PopChallenge)
| Field | Type | Description |
|---|---|---|
| warrant_id | string | Hex-encoded UUID of the warrant |
| tool | string | Tool being invoked |
| sorted_args | array | [(key, value), ...] sorted lexicographically by key |
| timestamp_window | i64 | floor(now / 30) * 30 |
Domain separation: The tenuo-pop-v1 prefix prevents cross-protocol signature reuse.
7.2 Signature Verification (Invariant I6)
POP_CONTEXT = b"tenuo-pop-v1"
def verify_pop(warrant, signature, tool, args, max_windows=4):
now = int(time.time())
sorted_args = sorted(args.items())
for i in range(max_windows):
window = ((now // 30) - i) * 30
challenge = (warrant.id, tool, sorted_args, window)
preimage = POP_CONTEXT + cbor_serialize(challenge)
if warrant.holder.verify(preimage, signature):
return True
return False
7.3 Time Window Enforcement
| Parameter | Value | Purpose |
|---|---|---|
| Context | tenuo-pop-v1 |
Domain separation |
| Window size | 30 seconds | Groups signatures into buckets |
| Max windows | 4 | ~2 minute total validity |
| Clock tolerance | ±30 seconds | Handles distributed clock skew |
7.4 Replay Protection
Tenuo is stateless by design. The Verifier does not track used PoP signatures. Defense layers:
| Layer | Protection |
|---|---|
| Short TTL | Warrants expire in minutes |
| PoP binding | Signature covers (warrant, tool, args, window) |
| Holder key | Attacker needs private key to use stolen PoP |
| Time windows | ~2 minute validity |
SHOULD: For high-value operations (financial transactions, irreversible actions), applications SHOULD implement server-side idempotency using
extensions["tenuo.dedup_key"]. The dedup key SHOULD be a hash of(warrant_id, tool, canonical_args)and SHOULD be checked before execution.
8. Serialization
8.1 Wire Format
SignedWarrant = CBOR Array [
0: envelope_version (u8),
1: payload (bytes),
2: signature (Signature),
]
Signature = CBOR Array [
0: algorithm (u8), // 1 = Ed25519
1: bytes (bytes),
]
WarrantPayload = CBOR Map {
0: version,
1: id,
2: warrant_type,
3: tools,
4: holder,
5: issuer,
6: issued_at,
7: expires_at,
8: max_depth,
9: parent_hash, // SHA256 of parent payload (optional)
10: extensions,
11: issuable_tools, // Issuer warrants only (optional)
12: (reserved),
13: max_issue_depth, // Issuer warrants only (optional)
14: constraint_bounds, // Issuer warrants only (optional)
15: required_approvers, // Multi-sig (optional)
16: min_approvals, // Multi-sig threshold (optional)
17: clearance, // Privilege level (optional)
18: depth, // Current delegation depth
// session_id is stored in extensions["tenuo.session_id"]
}
8.2 Deterministic CBOR Requirements (RFC 8949 §4.2)
- Map keys: Sorted by byte-wise lexicographic order
- Integers: Minimal encoding
- No indefinite-length arrays or maps
- Floats: IEEE 754 binary64
- Duplicate map keys: Senders MUST NOT produce; verifier behavior is undefined (see wire-format-v1.md)
Verifiers MUST reject non-deterministic encodings. Signatures are computed over deterministic CBOR only.
8.3 Transport Encoding
- Binary: Raw CBOR
- Text: Base64 URL-safe, no padding (RFC 4648 §5)
8.4 PEM Armor
-----BEGIN TENUO WARRANT-----
(Base64 of CBOR SignedWarrant)
-----END TENUO WARRANT-----
8.5 Signature Preimage
Warrant signatures are computed over:
preimage = b"tenuo-warrant-v1" || envelope_version || payload_bytes
This domain separation prevents cross-protocol signature reuse.
9. Protocol Limits
| Limit | Value | Purpose |
|---|---|---|
| MAX_DELEGATION_DEPTH | 64 | Prevents unbounded chains |
| MAX_WARRANT_TTL_SECS | 90 days | Protocol ceiling |
| MAX_WARRANT_SIZE | 64 KB | Memory exhaustion |
| MAX_STACK_SIZE | 256 KB | Chain size limit |
| PoP Window | 30 seconds | Replay protection |
| PoP Max Windows | 4 | Clock skew tolerance |
10. Error Codes
| Code | Description |
|---|---|
chain_not_anchored |
Root issuer not in trusted_roots |
signature_invalid |
Cryptographic verification failed |
warrant_expired |
Current time > expires_at |
depth_exceeded |
Depth > max_depth or > MAX_DELEGATION_DEPTH |
ttl_exceeded |
Child expires after parent |
attenuation_invalid |
Child capabilities exceed parent |
pop_failed |
PoP signature invalid or expired |
tool_not_allowed |
Tool not in warrant’s capabilities |
constraint_not_satisfied |
Argument violates constraint |
unknown_field |
Payload contains unknown CBOR keys |
self_issuance |
Holder cannot delegate to themselves (child.holder == parent.holder) |
revoked |
Warrant ID appears in active Signed Revocation List |
11. Revocation
11.1 Design Philosophy
Tenuo favors short TTLs over revocation. A 5-minute warrant that expires naturally is simpler and safer than a 24-hour warrant that might need revocation.
However, revocation is provided for cases where:
- Warrants have longer TTLs for operational reasons
- Key compromise is detected
- Policy violations require immediate termination
11.2 Signed Revocation List (SRL)
A Signed Revocation List is a verifier-side structure:
SignedRevocationList {
version: u64, // Monotonically increasing
issued_at: u64, // Unix timestamp
issuer: PublicKey, // Control Plane key
warrant_ids: array<string>, // Revoked warrant IDs
signature: Signature, // Signed by Control Plane
}
Properties:
- Signed by the Control Plane (same trust anchor as warrants)
- Versioned for update ordering; verifier MUST reject rollback to older version
- Additive - revocations are never removed; new SRLs only add entries
11.3 Revocation Request
Authorized parties can request revocation:
| Requestor | Can Revoke |
|---|---|
| Warrant holder | Their own warrant (surrender) |
| Warrant issuer | Warrants they issued |
| Control Plane | Any warrant |
RevocationRequest {
warrant_id: string,
requestor: PublicKey,
reason: string,
timestamp: u64,
signature: Signature,
}
11.4 Verifier Behavior
Verifiers receiving an SRL MUST:
- Verify the SRL signature against the Control Plane key
- Check that SRL version is ≥ current version (no rollback)
- Reject any warrant whose ID appears in the SRL
11.5 Design Trade-offs
| Approach | Tenuo Choice | Rationale |
|---|---|---|
| Online revocation check | No | Breaks stateless verifier design |
| Bloom filters | No | False positives unacceptable |
| Full SRL at verifiers | Yes | Simple, deterministic |
| Delta updates | Optional | Implementation detail |
12. Security Considerations
12.1 Cryptographic Assumptions
- Ed25519 signatures are unforgeable (128-bit security)
- SHA-256 is collision-resistant
- UUIDv7 provides sufficient entropy for warrant IDs
12.2 Implementation Pitfalls
| Pitfall | Consequence | Mitigation |
|---|---|---|
| Deserialize before verify | Signature bypass | Verify raw bytes first |
| Missing I1 check | Unauthorized delegation | Always verify child.issuer == parent.holder |
| PoP against issuer | Holder bypass | Always verify against warrant.holder |
| Strip unknown fields | Signature invalidation | Preserve and reject |
12.3 Deployment Considerations
- Use short TTLs (5-15 minutes) rather than revocation when possible
- Pin trusted_roots explicitly
- Log all authorization decisions for audit
- Consider SRL for high-security deployments
12.4 Common Misconfigurations
[!CAUTION] The following configurations create security vulnerabilities:
| Misconfiguration | Risk | Mitigation |
|---|---|---|
Empty trusted_roots |
Accepts any chain | Always configure trusted root keys |
| Missing PoP verification | Token theft | Require PoP for all agent requests |
| TTL > 1 hour | Revocation window too large | Use 5-15 minute TTLs |
| Ignoring unknown fields | Forward compatibility issues | Fail closed on unknown |
| Skipping depth check | Unbounded delegation | Enforce MAX_DELEGATION_DEPTH |
| Clearance not enforced | Privilege escalation | Verify clearance ≥ required |
13. Algorithm Agility
13.1 Current Algorithms
| Purpose | Algorithm | ID |
|---|---|---|
| Signatures | Ed25519 | 1 |
| Hashing | SHA-256 | - |
13.2 Extension Path
The algorithm field in Signature and PublicKey enables future algorithms:
| ID | Algorithm | Status |
|---|---|---|
| 1 | Ed25519 | Required |
| 2 | Ed448 | Reserved |
| 3-4 | ML-DSA (Dilithium) | Reserved (post-quantum) |
Verifiers MUST reject unknown algorithm IDs.
[!NOTE] Quantum Threats: Ed25519 is vulnerable to quantum attacks (Shor’s algorithm). Algorithm IDs 3-4 are reserved for NIST-approved post-quantum algorithms (ML-DSA/Dilithium). Deployments requiring long-term security (>10 years) should plan migration paths and ensure verifiers already reject unknown IDs.
Appendix A: Test Vectors
Test vectors are provided in test-vectors.md. They include:
- A.1 Minimal valid execution warrant
- A.3 Valid 3-level chain (root -> orchestrator -> worker)
- A.4 Invalid chain (I1 violation)
- A.5 Invalid chain (expired warrant)
- A.6 PoP verification case
Each test vector includes:
- Fixed key material (deterministic seeds)
- Fixed timestamps
- Payload CBOR (hex)
- Signature (hex)
Appendix B: Prior Art
| System | Relationship to Tenuo |
|---|---|
| Macaroons | Inspiration for attenuation; Tenuo adds PoP and structured constraints |
| Biscuit | Similar datalog-based approach; Tenuo is simpler, focused on AI agents |
| UCAN | JWT-based; Tenuo uses CBOR for compactness |
| CaMeL | Academic inspiration; Tenuo is production-focused implementation |
| OAuth 2.0 | Identity-based; Tenuo is capability-based |
Appendix C: Scope Boundaries
| Tenuo Owns | Tenuo Does NOT Own |
|---|---|
| Warrant format | Tool implementations |
| Constraint evaluation | Argument extraction |
| Chain verification | Transport security (TLS) |
| PoP verification | Key management |
| Attenuation rules | Policy authoring UI |
References
Normative
- [RFC 2119] Bradner, S., “Key words for use in RFCs to Indicate Requirement Levels”, BCP 14, March 1997. https://datatracker.ietf.org/doc/html/rfc2119
- [RFC 4648] Josefsson, S., “The Base16, Base32, and Base64 Data Encodings”, October 2006. https://datatracker.ietf.org/doc/html/rfc4648
- [RFC 8032] Josefsson, S., Liusvaara, I., “Edwards-Curve Digital Signature Algorithm (EdDSA)”, January 2017. https://datatracker.ietf.org/doc/html/rfc8032
- [RFC 8949] Bormann, C., Hoffman, P., “Concise Binary Object Representation (CBOR)”, December 2020. https://datatracker.ietf.org/doc/html/rfc8949
Informative
- [Dennis1966] Dennis, J.B., Van Horn, E.C., “Programming Semantics for Multiprogrammed Computations”, Communications of the ACM, Vol. 9, No. 3, March 1966. https://doi.org/10.1145/365230.365252
- [Macaroons] Birgisson, A., Politz, J.G., Erlingsson, U., Taly, A., Vrable, M., Lentczner, M., “Macaroons: Cookies with Contextual Caveats for Decentralized Authorization in the Cloud”, NDSS 2014. https://research.google/pubs/pub41892/
Changelog
- 1.0 (2026-01-10): Initial specification