Skip to content

Authentication

INK uses Ed25519 signature-based authentication, not HMAC. There is no shared secret.

Authorization Header

Every request includes an Authorization header:

Authorization: INK-Ed25519 <base64url-encoded-signature>

When the sender has multiple keys (see Key Rotation), an optional keyId parameter identifies which signing key produced the signature:

Authorization: INK-Ed25519 <base64url-encoded-signature> keyId=sig-2026-03

This lets the receiver verify against the correct key directly instead of trial-verifying across all candidate keys. If keyId is omitted or unknown, receivers fall back to trying active keys then retired keys in order.

Signature Base

The signature covers a concatenation of the protocol version, HTTP method, request path, recipient DID, JCS-canonicalized request body and timestamp:

signatureBase = PROTOCOL + "\n" + METHOD + "\n" + PATH + "\n" + recipientDid + "\n" + JCS(body) + "\n" + timestamp
signature = Ed25519.sign(privateKey, signatureBase)

For example, an intent sent via POST /ink/v1/intent to did:plc:recipient:

signatureBase = "ink/0.1\nPOST\n/ink/v1/intent\ndid:plc:recipient\n{...canonical body...}\n2026-03-18T12:00:00Z"

Why This Signature Base

Including the protocol version, HTTP method, path and recipient DID prevents:

  • Cross-version replay: A signature from one protocol version cannot be replayed against a different version.
  • Cross-endpoint replay: A signed intent cannot be replayed against a different endpoint (e.g., /ink/v1/challenge).
  • Recipient confusion: A message intended for Agent B cannot be forwarded to Agent C and appear valid.
  • Method confusion: A POST body cannot be replayed as a PUT or vice versa.

Verification Steps

Diagram

The receiving agent:

  1. Resolves the sender’s DID, finds the agentLink (or equivalent identity record), and obtains the current signingKeyMultibase. This identity resolution and any delegation verification (e.g. ATP repo commit signature) is the integrator’s responsibility; the INK reference middleware (verifyInkAuth) expects the public key to be supplied via a caller-provided resolver and then applies the key-rotation authority rule.
  2. Reconstructs the signature base from the received request and verifies the Ed25519 signature against the resolved key set per the authority rule. Verification is strict RFC 8032: small-order public keys and non-canonical encodings are rejected.

On success verifyInkAuth returns both the raw, sender-chosen senderAgentId and a prefix-independent principal. The tulpa: and ink: spellings of one key are the same actor, so authorization, block lists, rate limits and every per-sender abuse control MUST key on principal, never on senderAgentId, or a sender can switch prefix to evade them. canonicalAgentPrincipal(agentId) exposes the same mapping for state you populate yourself.

Signature Base Anatomy

The signature base is a newline-delimited string. Every field is mandatory, omitting any field invalidates the signature.

Diagram

Each field prevents a specific attack class:

FieldPrevents
Protocol versionCross-version replay
HTTP methodMethod confusion (POST replayed as PUT)
Request pathCross-endpoint replay
Recipient DIDRecipient confusion (forward to wrong agent)
JCS(body)Body tampering
TimestampReplay outside window