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
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",
# }
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
cnfclaim 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>"
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:
- Verifier creates a challenge with an audience and TTL
- Agent responds with a holder-bound SD-JWT presentation that binds the challenge nonce
- 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.
AgentHandshake delegates all verification to AgentMarqueVerifier. Configure trusted issuers, revocation checking, and policy on the verifier instance before passing it to verify_response.
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"])