Skip to content

Protocol

The LedgerFlow protocol defines a warrant format and proof-of-authorization model that extends x402 with agent-level payment authorization. The protocol is intentionally narrow: one warrant format, one proof format, one x402 extension namespace.

Design Philosophy

LedgerFlow borrows design lessons from Zanzibar and SpiceDB without adopting their full architecture:

  • Small public API — the merchant asks one question: "Is this agent authorized to use this quote?"
  • Typed constraints — context-aware checks without a generic expression engine
  • Time-bounded authority — expiration is a first-class citizen
  • Future extension points — consistency tokens (Zookies/ZedTokens) reserved for v2

LedgerFlow deliberately avoids:

  • General relationship graph stores
  • Recursive userset traversal
  • Schema compilation
  • Generic expression evaluation
  • Multi-hop object graph traversal on the hot path

The Warrant

A warrant is a self-contained, cryptographically signed authorization token.

Structure

Field Description
version Protocol version, explicit and versioned from day one
warrant_id Unique identifier for the warrant
issuer The entity that issued the warrant (signer identity)
subject_signer The agent's signer identity (verification key)
payment_subjects Settlement identities the agent may use
audience Which merchants the warrant applies to
not_before_ms Validity window start (Unix milliseconds)
expires_at_ms Validity window end (Unix milliseconds)
delegation Whether and how deep this warrant can be sub-delegated
constraints Typed constraint list
metadata Issuer-defined metadata
signature Cryptographic signature over canonical bytes

Identity Model

LedgerFlow separates two identity layers:

Signer Identity — used for cryptographic verification:

pub struct SignerRef {
    pub alg: SigningAlgorithm,   // Ed25519 or Secp256k1
    pub public_key: Vec<u8>,
    pub key_id: Option<String>,
}

Payment Subject — used for settlement, opaque to the merchant:

pub struct PaymentSubjectRef {
    pub kind: PaymentSubjectKind, // Caip10, FacilitatorAccount, ExchangeAccount, Opaque
    pub value: String,
}

Examples: caip10:eip155:8453:0xabc..., binance:uid:12345678, okx:subacct:agent-alpha.

This separation ensures merchants remain rail-agnostic.

Audience Scope

pub enum AudienceScope {
    MerchantIds(Vec<String>),
    MerchantHosts(Vec<String>),
    Any,
}

Prefer merchant IDs over hostnames when a stable identifier exists.

Delegation Policy

pub struct DelegationPolicy {
    pub can_delegate: bool,
    pub max_depth: u8,
}

Depth is sufficient for v1. No graph-style recursive delegation.

The Proof

The proof-of-authorization demonstrates that the agent holds the warrant and is using it for a specific transaction.

Proof Binding

The proof must bind to:

  • the LedgerFlow challenge ID
  • the warrant digest
  • the exact selected x402 accepted object
  • the current HTTP request
  • the signer identity
  • a freshness tuple (timestamp + nonce)

Canonical Request Hash

request_hash = SHA256(
  UPPER(method) || "\n" ||
  LOWER(authority) || "\n" ||
  path_and_query_exact || "\n" ||
  SHA256(body_bytes)
)

Intentionally simple. Transport headers excluded unless part of x402 payload.

Accepted Hash

accepted_hash is the SHA-256 digest of the canonical JSON serialization of the selected x402 accepted object.

This is the critical link that keeps LedgerFlow complementary to x402. The merchant quote is still x402. LedgerFlow only authorizes that quote.

Proof Preimage

proof_preimage = CBOR({
  domain: "ledgerflow-pop/v1",
  challenge_id: ...,
  warrant_digest: ...,
  accepted_hash: ...,
  request_hash: ...,
  created_at_ms: ...,
  nonce: ...,
  signer_key: ...
})

The proof signature is ED25519_SIGN(signer_key, SHA256(proof_preimage)).

Replay Protection

  • created_at_ms must fall within a 60-second verification window
  • Replay key: challenge_id + nonce with atomic TTL insert
  • Storage semantics: SET key value NX PX ttl_ms (Redis)
  • Never use non-atomic check-then-set

Idempotent Retries

  • Same proof + same payment identifier → return cached result
  • Same nonce with different request hash or accepted hash → reject as replay

Warrant Transport

Transport Modes

  1. Inline — warrant bytes sent on first use
  2. Digest reference — after merchant has cached the warrant

Rules:

  • inline_b64 present → merchant verifies and caches
  • Only digest present → merchant looks up cached warrant
  • Unknown digest → merchant rejects, asks client to resend inline

Cache Key

Cache by warrant_digest, never by signer alone.

warrant_digest = "sha256:" + hex(SHA256(canonical_signed_warrant_bytes))

Precise, portable, easy to implement.

What LedgerFlow Validates

The merchant-side verifier checks:

  • Warrant signature validity
  • Proof signature validity
  • Warrant validity window (not_before / expires_at)
  • Merchant scope against audience constraint
  • Resource scope against resource constraint
  • Tool scope against tool constraint
  • Asset and amount constraints
  • Delegation constraints
  • Proof binds to exact selected x402 accepted object
  • Proof binds to current HTTP request
  • Proof is not replayed

Protocol Limits

Limit Value
Max delegation depth 64
Max warrant TTL 90 days
Max warrant size 8 KB
Max constraints per warrant 32
Proof freshness window 60 seconds
Recommended nonce length 16 bytes

See Also