AgentHandshake

AgentHandshake provides a structured challenge-response protocol built on holder-bound SD-JWT presentations. A verifier creates a challenge, the agent responds with a presentation that proves both credential validity and holder key control, and the verifier checks everything in one step. All methods are static.

from agentmarque import AgentHandshake
STATICAgentHandshake.create_challenge(audience, ttl) -> dict

create_challenge

Create a challenge for an agent to respond to. Returns a dict containing a cryptographic nonce, the audience identifier, and timing fields.

Required attributes

  • Name
    audience
    Type
    str
    Description

    Identifier for the verifier (e.g., a service URL or agent DID). Bound into the challenge to prevent cross-service replay.

Optional attributes

  • Name
    ttl
    Type
    int
    Description

    Time-to-live in seconds. The challenge expires after this window.

Return value

A dict with keys: nonce (hex string), audience (str), created_at (ISO timestamp), expires_at (ISO timestamp).

Example

challenge = AgentHandshake.create_challenge(
    audience="https://api.verifier.com",
    ttl=120,
)
# {
#   "nonce": "a3f8c1d9...",
#   "audience": "https://api.verifier.com",
#   "created_at": "2026-04-02T12:00:00Z",
#   "expires_at": "2026-04-02T12:02:00Z",
# }

STATICAgentHandshake.respond(challenge, credential, holder_key, disclose) -> str

respond

Respond to a challenge by creating a holder-bound SD-JWT presentation. The agent signs a key-binding JWT (KB-JWT) over the challenge nonce with its holder key, proving it controls the key bound to the credential via the cnf claim.

Returns a presentation string in the format <issuer-signed-sd-jwt>~<disclosures>~<holder-signed-kb-jwt>.

Raises HandshakeError if signing fails.

Required attributes

  • Name
    challenge
    Type
    dict
    Description

    The challenge dict received from the verifier via create_challenge.

  • Name
    credential
    Type
    AgentCredential
    Description

    The agent's credential to present.

  • Name
    holder_key
    Type
    Ed25519PrivateKey
    Description

    The agent's Ed25519 private key matching the cnf claim in the credential.

Optional attributes

  • Name
    disclose
    Type
    list[str] | None
    Description

    Claim names to selectively disclose. When None, all claims are disclosed.

Example

presentation = AgentHandshake.respond(
    challenge=challenge,
    credential=my_credential,
    holder_key=my_private_key,
    disclose=["agentName", "capabilities"],
)
# "<issuer-jwt>~<disc1>~<disc2>~<kb-jwt>"

STATICAgentHandshake.verify_response(response, expected_challenge, verifier, ...) -> VerificationResult

verify_response

Verify an agent's handshake response. Delegates to the provided AgentMarqueVerifier to check the issuer signature, holder binding, nonce, audience, expiration, revocation status, and policy constraints. Returns a full VerificationResult.

Raises HandshakeError if the presentation is malformed or the verifier encounters an unrecoverable error.

Required attributes

  • Name
    response
    Type
    str
    Description

    The presentation string from respond.

  • Name
    expected_challenge
    Type
    dict
    Description

    The original challenge dict. Used to check the nonce and audience.

  • Name
    verifier
    Type
    AgentMarqueVerifier
    Description

    A configured verifier instance. All cryptographic and policy checks are delegated to this verifier.

Optional attributes

  • Name
    min_tier
    Type
    int
    Description

    Minimum required verification tier.

  • Name
    min_reputation
    Type
    float
    Description

    Minimum required reputation score.

  • Name
    required_capabilities
    Type
    list[str] | None
    Description

    Capabilities the agent must have.

  • Name
    check_revocation
    Type
    bool
    Description

    Whether to check the credential's revocation status.

Example

result = AgentHandshake.verify_response(
    response=presentation,
    expected_challenge=challenge,
    verifier=my_verifier,
    min_tier=1,
    min_reputation=50.0,
    required_capabilities=["translate"],
)

if result.valid:
    print(result.claims["agentName"])
else:
    for err in result.errors:
        print(f"  - {err}")

Full Flow

The handshake protocol has three steps:

  1. Verifier creates a challenge with an audience and TTL
  2. Agent responds with a holder-bound SD-JWT presentation that binds the challenge nonce
  3. Verifier checks the presentation — issuer signature, holder binding, nonce, audience, and policy — via AgentMarqueVerifier

This proves the agent holds a valid credential and controls the private key bound to it, in a single round trip.

Complete example

from agentmarque import (
    AgentHandshake,
    AgentMarqueVerifier,
)

# Setup: verifier instance
verifier = AgentMarqueVerifier(
    trusted_issuers=["did:key:z6MkIssuer..."],
)

# 1. Verifier creates challenge
challenge = AgentHandshake.create_challenge(
    audience="https://api.verifier.com",
    ttl=120,
)

# 2. Agent responds (on agent side)
presentation = AgentHandshake.respond(
    challenge=challenge,
    credential=agent_credential,
    holder_key=agent_private_key,
    disclose=["agentName", "capabilities"],
)

# 3. Verifier checks the response
result = AgentHandshake.verify_response(
    response=presentation,
    expected_challenge=challenge,
    verifier=verifier,
    min_tier=1,
)
assert result.valid, result.errors
print(result.claims["agentName"])

Was this page helpful?