{"type":"text/markdown","content":"# HOLA Protocol - Inter-Agent Authentication\n\nComplete guide to the HOLA protocol for proving your identity to other agents using cryptographic signatures.\n\n## Table of Contents\n\n- [Quick Start: HOLA Generation Pattern](#quick-start-hola-generation-pattern)\n- [Overview](#overview)\n- [When is a HOLA validated?](#when-is-a-hola-validated)\n- [When to Use](#when-to-use)\n- [Prerequisites](#prerequisites)\n- [Vocabulary (HOLA)](#vocabulary-hola)\n- [HOLA Message Format](#hola-message-format)\n- [Canonical form vs on-the-wire appearance](#canonical-form-vs-on-the-wire-appearance)\n- [Authentication Flow](#authentication-flow)\n- [Step 1: Get JWT Token](#step-1-get-jwt-token)\n- [Step 2: Request Nonce](#step-2-request-nonce)\n- [Step 3: Construct HOLA Message](#step-3-construct-hola-message)\n- [Step 3.5: Canonicalization (CRITICAL)](#step-35-canonicalization-critical)\n- [Step 3.6: Sign the Canonical Message](#step-36-sign-the-canonical-message)\n- [Step 3.7: Checksum Calculation](#step-37-checksum-calculation)\n- [Complete Pattern Example](#complete-pattern-example)\n- [Step 4: Send HOLA to Peer](#step-4-send-hola-to-peer)\n- [Step 5: Verify HOLA (Peer Agent) — HTTP API path](#step-5-verify-hola-peer-agent--http-api-path)\n- [Verify Your HOLA Works](#verify-your-hola-works)\n- [Common Pitfalls](#common-pitfalls)\n- [Debugging HOLA Failures](#debugging-hola-failures)\n- [Next Steps](#next-steps)\n\n## Quick Start: HOLA Generation Pattern\n\n**Reference (IdentyClaw)** — API `https://api.identyclaw.com`. Login and nonce retrieval use curl + Bearer JWT (no client-side NEAR RPC for server validation). HOLA line signing below is the proven wire format from this deployment.\n\n**⚠️ CRITICAL:** HOLA messages are time-sensitive — generate fresh timestamps and nonces for each message.\n\n### Complete Flow Pattern\n\n```javascript\n// Reference (IdentyClaw) wire format — your values will differ each run\nconst nacl = require('tweetnacl');\nconst base32 = require('hi-base32');\nconst bs58 = require('bs58');\n// 1. Get fresh JWT token (see Step 1 for details)\nconst jwt = await getJWT(); // Bearer token (see Step 1);\n// 2. Request a fresh nonce for this HOLA message\nconst nonceResponse = await fetch('https://api.identyclaw.com/api/holanonce16ts', {\n  headers: { 'Authorization': `Bearer ${jwt}` }\n});\nconst { noncetsHex, timestamp } = await nonceResponse.json();\n// Example response: { noncetsHex: \"A1B2C3D4E5F6...\", timestamp: \"2026-05-04T10:09:00.000Z\" }\n// 3. Build message (everything before signature)\nconst recipient = 'MUNDO';\nconst tokenId = 'bjbvcjzqbdsj'; // Your 12-letter passport ID;\nconst message = `HOLA/${recipient}/${tokenId}/${timestamp}/${noncetsHex}/API.IDENTYCLAW.COM/`;\n// 4. Canonicalize (UPPERCASE everything)\nconst canonicalMessage = message.toUpperCase();\n// Result: \"HOLA/MUNDO/BJBVCJZQBDSJ/2026-05-04T10:09:00.000Z/A1B2C3D4E5F6.../API.IDENTYCLAW.COM/\"\n// 5. Sign the canonical message\nconst messageBytes = new TextEncoder().encode(canonicalMessage);\nconst signature = nacl.sign.detached(messageBytes, yourSecretKey);\nconst signatureB32 = base32.encode(Buffer.from(signature)).replace(/=+$/, '').toUpperCase();\n// Example: \"MFRGG2LTMVZXGZLSN5XWC3TBNRQW4ZDJMQFA...\"\n// 6. Calculate checksum\nconst checksumPrefix = `${canonicalMessage}${signatureB32}/`;\nconst sum = 0;\nsum += checksumPrefix.charCodeAt(i);\nconst holaChecksumAlphabet = 'ABCDEFGHJKMNPQRSTUVWXYZ';\nconst checksum = holaChecksumAlphabet[sum % 23];\n// Example: sum=12543, checksum='J' (index 8 in alphabet; omits I, L, O)\n// 7. Build final HOLA\nconst hola = `${canonicalMessage}${signatureB32}/${checksum}`;\n// Example result: \"HOLA/MUNDO/BJBVCJZQBDSJ/2026-05-04T10:09:00.000Z/A1B2.../API.IDENTYCLAW.COM/MFRGG.../J\"\n```\n\n**Key Points**:\n- ✓ Generate **fresh nonce** for each HOLA (5-minute validity)\n- ✓ Use **current timestamp** from nonce response\n- ✓ **Uppercase** entire message before signing\n- ✓ Sign with **Ed25519 private key** of passport owner\n- ✓ Encode signature as **base32** (not base64)\n- ✓ Calculate **checksum** on canonical message + signature + `/`\n- ✓ **Wire presentation**: You do **not** need to send the line in all capitals; verifiers normalize casing. You still sign the uppercase canonical prefix ([Canonical form vs on-the-wire appearance](#canonical-form-vs-on-the-wire-appearance))\n\n## Overview\n\nHOLA (Hello Authentication) is a **peer-to-peer, offline** protocol: agents prove Passport identity to each other with signed **HOLA lines** on whatever channel already carries their conversation. HOLA is **not** exchanged through a central broker for mutual authentication. Each peer **verifies independently** — **IdentyClaw API** (`POST /api/identity/verify`) or **direct NEAR RPC** (e.g. `@rodit/rodit-auth-be`), whichever that peer chooses.\n\n### When is a HOLA validated?\n\nVerifying the checksum locally, or checking that an Ed25519 signature verifies against a public key you fetched yourself, is **not** sufficient to treat a received HOLA as authenticated for trust or authorization. Those checks only show that the string is well-formed and that *some* key signed *something* matching that payload.\n\n**Full validation** means applying the complete proof bar: on-chain Passport state (exists, active, not expired), signature and checksum validity, fresh timestamp, nonce replay safety, and recipient binding. Each peer **verifies independently**, using either path below:\n\n| Path | When to use |\n| --- | --- |\n| **Direct NEAR RPC** | Peer verifies via chosen RPC + `@rodit/rodit-auth-be` (or equivalent) |\n| **Via IdentyClaw HTTP API** | Peer calls `POST /api/identity/verify` (documented in detail below) |\n\nHOLA **exchange** is offline peer-to-peer — neither path brokers the wire. Either path may be used for **validation**.\n\n**This guide documents the HTTP API path** because it is what IdentyClaw implements. It does **not** mean Passport holders must route peer trust through this API.\n\n**When using the HTTP API path:** treat an inbound HOLA as validated only after `POST /api/identity/verify` completes successfully with an outcome that confirms verification—for example `verified: true` with passing checks and no blocking failures (see [Step 5](#step-5-verify-hola-peer-agent--http-api-path)). Until then, do not grant access, secrets, tools, or elevated behavior based on checksum or bare signature checks alone.\n\n`POST /api/testhola` is for diagnosing **your own** HOLA generation. It is **not** a substitute for full validation when deciding whether to trust **another agent's** HOLA.\n\n**Trust the API host, not just TLS (HTTP path only):** pin `https://api.identyclaw.com` (or use `@rodit/rodit-auth-be` `RoditClient.login_server()` when your flow uses JWT). A look-alike host could return `verified: true` for untrusted HOLA lines. Optional JWT on verify enables `RECIPIENT_MISMATCH` warnings only. See [finding-agents.md](finding-agents.md) and [Verify the API server (MITM protection)](login-authentication.md#verify-the-api-server-mitm-protection).\n\nConceptual background: [`public/policies/why-identyclaw.md`](../public/policies/why-identyclaw.md) §3.1 and §8.1.\n\n**Normative checklist:** [`identity-verification-policy.md`](identity-verification-policy.md) — family matching, partner vs peer (login lane), optional controlling-address checks, and subagent extensions.\n\n### Envelope vs HOLA line\n\nHandle your own messaging or routing outside this protocol however you need. Put Passport proof only in the **HOLA line**—not inside unrelated envelope or routing fields.\n\n### HOLA Format Boundaries\n\n- Send HOLA as one slash-separated string in the request payload: `{\"hola\": \"<single HOLA string>\"}`.\n- Build HOLA signatures from the **HOLA canonical prefix** using **base32** only (RFC 4648 on the signed prefix).\n- Use the ISO 8601 timestamp returned by `GET /api/holanonce16ts` for HOLA.\n- **Capitalization on the wire is optional**: nothing requires an all-uppercase transmitted line. **Stylistic choices that fit your agent’s identity are welcome** (for example a lowercase `hola/` greeting), as long as the cryptographic steps use the canonical uppercase signed payload ([Canonical form vs on-the-wire appearance](#canonical-form-vs-on-the-wire-appearance)).\n\n**Self-diagnostic loop:** Use `POST /api/testhola` to troubleshoot HOLA format and signature issues. Each error response includes `details.documentation.see` (paths into `references/`) and `details.example` (a corrected value for the failing field), so you can iterate without leaving the endpoint. It does **not** replace `POST /api/identity/verify` when you must decide whether another agent’s HOLA is trustworthy ([When is a HOLA validated?](#when-is-a-hola-validated)).\n\n## When to Use\n\nUse HOLA Protocol when you need to:\n\n- Prove your identity to another agent\n- Establish trust with a peer agent\n- Implement agent-to-agent communication\n- Enable decentralized verification — peers apply the full proof bar directly or via a verification service (see [When is a HOLA validated?](#when-is-a-hola-validated))\n- **You ALREADY have a bearer token** for IdentyClaw HTTP calls (see [Prerequisites](#prerequisites))\n\n## Prerequisites\n\nBefore using HOLA, you must:\n\n1. **Have a valid JWT token** - Complete [API Login](login-authentication.md) first\n2. **Know your Passport ID** - Your 12-letter identity (e.g., `bkbvehbdcrgm`)\n3. **Have access to your NEAR private key** - From the account that owns your Passport\n\n## Vocabulary (HOLA)\n\nThis guide uses the following terms consistently. **HTTP fields stay as defined in OpenAPI** (for example the JSON property `hola`, responses from `GET /api/holanonce16ts`).\n\n| Term | Meaning |\n| --- | --- |\n| **HOLA line** | Full slash-separated wire string (for example the JSON `hola` field on verify/test endpoints) |\n| **HOLA canonical prefix** | UTF-8 bytes from `HOLA/` through `API.IDENTYCLAW.COM/`, uppercased for signing |\n| **HOLA nonce** | JSON field `noncetsHex` from `GET /api/holanonce16ts` (32 uppercase hex characters in the line) |\n| **HOLA timestamp** | JSON field `timestamp` from the same response (ISO-8601; not login `timestamp_iso`) |\n| **base32 line signature** | Ed25519 signature over the canonical prefix, base32-encoded for the line |\n| **Bearer token** | Credential in `Authorization: Bearer …` for IdentyClaw endpoints that require a session (nonce fetch, verification, etc.); **not** a substitute for sending a **HOLA line** to a peer |\n\n## HOLA Message Format\n\n**Structure**:\n```\nHOLA/<recipient>/<tokenId>/<ISO8601-timestamp>/<noncets-hex>/API.IDENTYCLAW.COM/<base32-signature>/<checksum>\n```\n\n**Components**:\n\n| Field | Description | Example |\n|-------|-------------|---------|\n| `HOLA/` | Protocol identifier | `HOLA/` |\n| `recipient` | Intended recipient (default: MUNDO) | `MUNDO` or `abcdefghijkl` |\n| `tokenId` | Sender's Passport ID (12 lowercase letters) | `bkbvehbdcrgm` |\n| `timestamp` | ISO 8601 timestamp | `2026-04-19T10:47:00.000Z` |\n| `noncets-hex` | 32 uppercase hex characters (16 bytes) from `/api/holanonce16ts` | `4F9A3C7E2D1B9A4CDEADBEEFCAFEBABE` |\n| `API.IDENTYCLAW.COM` | Domain identifier | `API.IDENTYCLAW.COM` |\n| `signature` | base32-encoded Ed25519 signature (RFC 4648, A-Z2-7, uppercase, no padding) | `N3FZ5KQ8LH2BSM1XY` |\n| `checksum` | Single letter from `ABCDEFGHJKMNPQRSTUVWXYZ` (23 letters; omits **I**, **L**, **O**) | `J` |\n\n**Example HOLA Message**:\n```\nHOLA/MUNDO/bkbvehbdcrgm/2026-04-19T10:47:00.000Z/4F9A3C7E2D1B9A4CDEADBEEFCAFEBABE/API.IDENTYCLAW.COM/dGVzdHNpZ25hdHVyZQ/M\n```\n\n## Canonical form vs on-the-wire appearance\n\nHOLA distinguishes two ideas: **what must be signed and checksummed**, and **what characters may appear when you transmit the line**.\n\n### Cryptographic canonical form\n\nVerification always uses a single deterministic UTF-8 string: the segment from `HOLA/` through `API.IDENTYCLAW.COM/` with **that entire prefix uppercased** (see [Step 3.5](#step-35-canonicalization-critical)). You sign those octets with Ed25519, encode the signature as base32 (RFC 4648, uppercase A–Z and 2–7, no padding), then compute the checksum over the canonical uppercase prefix plus the base32 signature and a trailing `/`. If any of those steps use a different casing for the signed payload, the signature or checksum will not match.\n\n### Presentation on the wire (optional)\n\n**Sending an all-uppercase line is not required.** The protocol does not prescribe “official-looking” caps on the wire. Many examples use uppercase because the signing step uses an uppercase canonical prefix and copying that same string is convenient—not because peers must shout `HOLA`.\n\n**Creative or distinctive presentation that reflects your agent’s identity is welcome**: tone in logs or UI, a lowercase `hola/` if that fits your persona, or any harmless stylistic choice—provided you still produce a valid signature and checksum over the **canonical uppercase** signed bytes above. IdentyClaw HTTP validators normalize letter casing when parsing and reconstruct that canonical payload for verification. **`POST /api/testhola` uses the same normalization** and does not reject a message solely because the prefix was sent as `hola/` instead of `HOLA/`.\n\n### Signature field (base32)\n\nThe signature itself is base32 (RFC 4648) with an uppercase alphabet by specification. That is separate from how you choose to spell the protocol keyword on the wire.\n\n## Authentication Flow\n\n```\nAgent A (Initiator):\n1. POST /api/login → Get JWT\n2. GET /api/holanonce16ts → Get nonce\n3. Construct HOLA message with signature\n4. Send HOLA to Agent B\n\nAgent B (Verifier):\n5. POST /api/login → Get JWT (if not already authenticated)\n6. POST /api/identity/verify → Full server validation of Agent A's HOLA (required before trusting)\n7. Trust established only after verify succeeds (see When is a HOLA validated? above)\n```\n\n**Note:** Steps 5–6 describe the IdentyClaw HTTP API path. Peers may instead validate on-chain and cryptographically without calling this API ([When is a HOLA validated?](#when-is-a-hola-validated)).\n\n## Step 1: Get JWT Token\n\nYou must have a valid JWT token before requesting nonces. See [API Login Authentication](login-authentication.md) for complete instructions.\n\n**Quick summary**:\n```bash\n# 1. Get fresh one-time timestamp challenge\ncurl https://api.identyclaw.com/api/login/timestamp\n# 2. Sign message: accountid + timestamp_iso\n# 3. POST to login\ncurl -X POST https://api.identyclaw.com/api/login \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"accountid\": \"43d3c5b5e77a46b52933bc7a8b79b06f16dd4ca3cfbacd0e6fede0e7e01782ac\", \"timestamp\": 1776622758, \"base64url_signature\": \"...\"}'\n```\n\nUse each login timestamp pair once. If login fails, fetch a new pair and retry.\n\n## Step 2: Request Nonce\n\n⚠️ **CRITICAL: Which Key Pair to Use**\n\nYou must use the private key of the NEAR account that **currently owns** your IdentyClaw Passport.\n\n**Key Requirements:**\n1. Use the private key of the **current owner account** of your Passport\n2. Private key location: `~/.near-credentials/mainnet/<account_id>.json`\n3. If you've transferred your Passport to a different account, use that account's credentials (see [Key rotation](key-rotation.md))\n\n---\n\n**Endpoint**: `GET /api/holanonce16ts` (requires JWT)\n\n```bash\ncurl https://api.identyclaw.com/api/holanonce16ts \\\n  -H \"Authorization: Bearer YOUR_JWT\"\n```\n\n**Response Pattern** (values shown are illustrative only):\n```json\n{\n  \"noncetsHex\": \"4F9A3C7E2D1B9A4CDEADBEEFCAFEBABE\",\n  \"timestamp\": \"2026-04-19T10:47:00.000Z\",\n  \"length\": 16,\n  \"algorithm\": \"randomBytes(16)_hex\",\n  \"requestId\": \"01HQXYZ...\"\n}\n```\n\n**Do not confuse with login:** `GET /api/login/timestamp` returns `timestamp` + `timestamp_iso` for API login only. HOLA uses **`GET /api/holanonce16ts`** with JSON keys **`noncetsHex`** and **`timestamp`** only (not `timestamp_iso`, `nonceHex`, or `noncets`). Canonical reference: [holanonce-api.md](holanonce-api.md).\n\n**⚠️ CRITICAL - Nonce Freshness**:\n- Nonces are valid for approximately **5 minutes**\n- Generate a **NEW nonce** for each HOLA message\n- Treat documentation nonces as examples only; request and use fresh runtime nonce values\n- Use the `noncetsHex` and `timestamp` values **immediately** after receiving them\n\n**Extract nonce hex from response**: Use the `noncetsHex` field directly (already uppercase): `4F9A3C7E2D1B9A4CDEADBEEFCAFEBABE`\n\n## Step 3: Construct HOLA Message\n\n**Message to sign** (everything before the signature field):\n```\nHOLA/<recipient>/<tokenId>/<timestamp>/<noncets-hex>/API.IDENTYCLAW.COM/\n```\n\n**Reference (IdentyClaw) — wire format** (your values will differ):\n```\nHOLA/MUNDO/bkbvehbdcrgm/2026-04-19T10:47:00.000Z/4F9A3C7E2D1B9A4CDEADBEEFCAFEBABE/API.IDENTYCLAW.COM/\n```\n\n⚠️ Use the **fresh timestamp and nonce** from Step 2, not values from this example.\n\n## Step 3.5: Canonicalization (CRITICAL)\n\nThe signed payload is always the **uppercase canonical prefix** through `API.IDENTYCLAW.COM/`. How you **transmit** that line is up to you; verifiers normalize casing (see [Canonical form vs on-the-wire appearance](#canonical-form-vs-on-the-wire-appearance)).\n\n**Before signing, convert ALL components to UPPERCASE:**\n\n⚠️ **This is the most common source of HOLA signature failures.**\n\n**Components to uppercase:**\n1. **recipient** → `MUNDO` (already uppercase)\n2. **tokenId** → `BKBVEHBDCRGM` (even though stored as lowercase in database)\n3. **timestamp** → `2026-04-19T10:47:00.000Z` (verify 'Z' is uppercase)\n4. **noncetsHex** → `4F9A3C7E2D1B9A4CDEADBEEFCAFEBABE` (already uppercase from API)\n5. **domain** → `API.IDENTYCLAW.COM` (already uppercase)\n\n**Reference (IdentyClaw) — wire format** (use your fresh values from Step 2):\n```javascript\n// Original message components (use YOUR fresh values)\nconst recipient = 'MUNDO';\nconst tokenId = 'bkbvehbdcrgm';  // Your 12-letter passport ID (lowercase from storage);\nconst timestamp = '2026-04-19T10:47:00.000Z';  // From Step 2 nonce response;\nconst noncetsHex = '4F9A3C7E2D1B9A4CDEADBEEFCAFEBABE';  // From Step 2 nonce response;\n// Build message\nconst message = `HOLA/${recipient}/${tokenId}/${timestamp}/${noncetsHex}/API.IDENTYCLAW.COM/`;\n// Canonicalize: UPPERCASE EVERYTHING\nconst canonicalMessage = message.toUpperCase();\n// Result: \"HOLA/MUNDO/BKBVEHBDCRGM/2026-04-19T10:47:00.000Z/4F9A3C7E2D1B9A4CDEADBEEFCAFEBABE/API.IDENTYCLAW.COM/\"\n// Sign the canonical message\nconst messageBytes = new TextEncoder().encode(canonicalMessage);\nconst signature = nacl.sign.detached(messageBytes, secretKey);\n```\n\n⚠️ **Critical**: Sign the **canonicalized (uppercase) message**, not the original case.\n\n## Step 3.6: Sign the Canonical Message\n\n**Signing:** Ed25519 detached over UTF-8 bytes of the **HOLA canonical prefix**; encode the result as **base32** for the HOLA line (not base64url).\n\n1. Convert **canonical (uppercase)** prefix to UTF-8 bytes\n2. Sign with Ed25519 secret key\n3. Encode signature as **base32** (RFC 4648, A-Z2-7, uppercase, no padding)\n\n**Reference (IdentyClaw) — JavaScript wire format** (use your fresh values):\n```javascript\nconst nacl = require('tweetnacl');\nconst base32 = require('hi-base32');\nconst bs58 = require('bs58');\nconst fs = require('fs');\n// 1. Load credentials\nconst creds = JSON.parse(fs.readFileSync('~/.near-credentials/mainnet/your-account.json'));\nconst privateKeyBase58 = creds.private_key.replace('ed25519:', '');\n// 2. Decode keypair\nconst keypair = bs58.decode(privateKeyBase58);\nconst secretKey = keypair.slice(0, 32);\n// 3. Build and canonicalize message (use YOUR fresh timestamp and nonce from Step 2)\nconst message = `HOLA/MUNDO/bkbvehbdcrgm/2026-04-19T10:47:00.000Z/4F9A3C7E2D1B9A4CDEADBEEFCAFEBABE/API.IDENTYCLAW.COM/`;\nconst canonicalMessage = message.toUpperCase();\n// 4. Sign the canonical message\nconst messageBytes = new TextEncoder().encode(canonicalMessage);\nconst signature = nacl.sign.detached(messageBytes, secretKey);\n// 5. Encode as base32 (RFC 4648: A-Z2-7, uppercase, no padding)\nconst signatureB32 = base32.encode(Buffer.from(signature)).replace(/=+$/, '').toUpperCase();\n// Example result: \"MFRGG2LTMVZXGZLSN5XWC3TBNRQW4ZDJMQFA...\" (yours will differ)\n```\n\n**Reference (IdentyClaw) — Python wire format** (use your fresh values):\n```python\nfrom nacl.signing import SigningKey\nimport base58\nimport json\nimport base64\n# 1. Load credentials\ncreds = json.load(f)\nprivate_key_base58 = creds['private_key'].replace('ed25519:', '')\n# 2. Decode keypair\nkeypair = base58.b58decode(private_key_base58)\nsecret_key = keypair[:32]\n# 3. Build and canonicalize message (use YOUR fresh timestamp and nonce from Step 2)\nmessage = 'HOLA/MUNDO/bkbvehbdcrgm/2026-04-19T10:47:00.000Z/4F9A3C7E2D1B9A4CDEADBEEFCAFEBABE/API.IDENTYCLAW.COM/'\ncanonical_message = message.upper()\n# 4. Sign the canonical message\nsigning_key = SigningKey(secret_key)\nsignature = signing_key.sign(canonical_message.encode('utf-8')).signature\n# 5. Encode as base32 (RFC 4648)\nimport base64\nsignature_b32 = base64.b32encode(signature).decode('utf-8').rstrip('=').upper()\n# Example result: \"MFRGG2LTMVZXGZLSN5XWC3TBNRQW4ZDJMQFA...\" (yours will differ)\n```\n\n## Step 3.7: Checksum Calculation\n\n**Alphabet (fixed order, length 23):** `ABCDEFGHJKMNPQRSTUVWXYZ` (uppercase Latin letters **without** **I**, **L**, **O**).\n\n**Algorithm**: Let `checksumPrefix` be the canonical uppercase signed payload through `API.IDENTYCLAW.COM/`, plus the base32 signature, plus a trailing `/`. Sum **UTF-16 code units** (same as JavaScript `charCodeAt` over that string—ASCII-only HOLA components match byte sums). Then `checksum = alphabet[sum % 23]`.\n\nThe checksum is calculated on the **canonicalized (uppercase) message + signature + trailing slash**.\n\n### Canonical string (signing and checksum)\n\nUse **one** uppercase prefix for both Ed25519 signing and the checksum (the signature is appended only for the checksum step):\n\n1. Build the signed prefix (field order and slashes matter):\n\n```\nHOLA/<recipient>/<tokenId>/<ISO-8601-timestamp>/<noncets-hex>/API.IDENTYCLAW.COM/\n```\n\n| Field | Meaning |\n|-------|---------|\n| `recipient` | Intended recipient (often `MUNDO`; may be another agent's 12-letter Passport ID when addressing them directly) |\n| `tokenId` | **Sender's** Passport id (12 letters; often stored lowercase, uppercased in canonical form) |\n| `noncets-hex` | 32 hex characters from `GET /api/holanonce16ts` (16 random bytes as hex — not 16 hex chars) |\n\n2. **Canonicalize:** `canonicalMessage = message.toUpperCase()` on the **entire** prefix string.\n3. **Sign:** Ed25519 detached signature over UTF-8 bytes of `canonicalMessage`.\n4. **Encode:** RFC 4648 base32, uppercase `A–Z2–7`, no `=` padding → `signatureB32` (always **103 characters** for Ed25519).\n5. **Checksum input:** `checksumPrefix = canonicalMessage + signatureB32 + \"/\"` — include the trailing `/`; do **not** include the checksum letter.\n\n**Self-test:** After assembling your HOLA, call `POST /api/testhola` while building as the sender. When validating a peer's HOLA, use `POST /api/identity/verify`. A checksum mismatch returns `expected` vs `got` in the error message — use that to diff your `checksumPrefix`.\n\n```javascript\n// Step 1: Build canonical message (already uppercase)\nconst canonicalMessage = message.toUpperCase();\n// Example: \"HOLA/MUNDO/BKBVEHBDCRGM/2026-04-19T10:47:00.000Z/4F9A3C7E2D1B9A4CDEADBEEFCAFEBABE/API.IDENTYCLAW.COM/\"\n// Step 2: Add signature and trailing slash\nconst checksumPrefix = `${canonicalMessage}${signatureB32}/`;\n// Example: \"HOLA/MUNDO/.../API.IDENTYCLAW.COM/MFRGG2LTMVZXGZLSN5XWC3TBNRQW4ZDJMQFA.../\"\n// Step 3: Sum UTF-16 code units (charCodeAt)\nconst sum = 0;\nsum += checksumPrefix.charCodeAt(i);\n// Example: sum = 12543\n// Step 4: Modulo 23 and pick letter from fixed alphabet\nconst holaChecksumAlphabet = 'ABCDEFGHJKMNPQRSTUVWXYZ';\nconst checksum = holaChecksumAlphabet[sum % 23];\n// Example: 12543 % 23 = 8 → alphabet[8] = 'J'\n```\n\n**Full checksum input example** (one continuous line; signature is illustrative — yours will differ):\n\n```\nHOLA/MUNDO/BKBVEHBDCRGM/2026-04-19T10:47:00.000Z/4F9A3C7E2D1B9A4CDEADBEEFCAFEBABE/API.IDENTYCLAW.COM/FCHWM6BHKU43A4GB2HO4J455TH3AHAV4WCJJOWBS3VLWSZDRFKR6PYCV7RTPLYABEUY75BOQ4ONOOJ3N4U5JAHB2YECVNDMISI3DUDI/\n```\n\nSum of UTF-16 code units over that string: 13807 → `13807 % 23 = 7` → checksum **`H`** (index 7 in `ABCDEFGHJKMNPQRSTUVWXYZ`).\n\n**Critical Notes**:\n- Checksum is calculated on the **uppercase canonical message**\n- Include the **trailing slash** after the signature: `\"...SIGNATURE/\"`\n- Use `charCodeAt()` (JavaScript) or equivalent UTF-16 code units over `checksumPrefix`\n- Result is always **one uppercase letter** from `ABCDEFGHJKMNPQRSTUVWXYZ`\n- This is **NOT** a cryptographic hash (it's a simple integrity check)\n\n**Final HOLA Pattern** (same values as the checksum example above; signature is illustrative):\n\n```\nHOLA/MUNDO/BKBVEHBDCRGM/2026-04-19T10:47:00.000Z/4F9A3C7E2D1B9A4CDEADBEEFCAFEBABE/API.IDENTYCLAW.COM/FCHWM6BHKU43A4GB2HO4J455TH3AHAV4WCJJOWBS3VLWSZDRFKR6PYCV7RTPLYABEUY75BOQ4ONOOJ3N4U5JAHB2YECVNDMISI3DUDI/H\n```\n\n## Complete Pattern Example\n\n**⚠️ Complete working code pattern - generate fresh values each time**\n\n### JavaScript Complete Implementation\n\n```javascript\nconst nacl = require('tweetnacl');\nconst base32 = require('hi-base32');\nconst bs58 = require('bs58');\nconst fs = require('fs');\nasync function generateHOLA(tokenId, jwtToken) {\n// 1. Get fresh nonce for this run\nconst nonceResponse = await fetch('https://api.identyclaw.com/api/holanonce16ts', {\n  headers: { 'Authorization': `Bearer ${jwtToken}` }\n});\n  throw new Error(`Failed to fetch nonce: ${nonceResponse.status}`)\nconst { noncetsHex, timestamp } = await nonceResponse.json();\n// Example: { noncetsHex: \"A1B2C3D4E5F6...\", timestamp: \"2026-05-04T10:09:00.000Z\" }\n// 2. Load your NEAR credentials\nconst creds = JSON.parse(fs.readFileSync(;\n`~/.near-credentials/mainnet/${tokenId}.near.json`;\n));\nconst privateKeyBase58 = creds.private_key.replace('ed25519:', '');\nconst keypair = bs58.decode(privateKeyBase58);\nconst secretKey = keypair.slice(0, 32);\n// 3. Build message\nconst recipient = 'MUNDO';\nconst message = `HOLA/${recipient}/${tokenId}/${timestamp}/${noncetsHex}/API.IDENTYCLAW.COM/`;\n// 4. Canonicalize (UPPERCASE)\nconst canonicalMessage = message.toUpperCase();\n// 5. Sign\nconst messageBytes = new TextEncoder().encode(canonicalMessage);\nconst signature = nacl.sign.detached(messageBytes, secretKey);\nconst signatureB32 = base32.encode(Buffer.from(signature)).replace(/=+$/, '').toUpperCase();\n// 6. Calculate checksum\nconst checksumPrefix = `${canonicalMessage}${signatureB32}/`;\nconst sum = 0;\nsum += checksumPrefix.charCodeAt(i);\nconst holaChecksumAlphabet = 'ABCDEFGHJKMNPQRSTUVWXYZ';\nconst checksum = holaChecksumAlphabet[sum % 23];\n// 7. Build final HOLA\nconst hola = `${canonicalMessage}${signatureB32}/${checksum}`;\nreturn {\n  hola,\n  tokenId,\n  recipient,\n  timestamp,\n  noncetsHex,\n  signatureB32,\n  checksum\n};\n// Usage:\n// const result = await generateHOLA('bjbvcjzqbdsj', yourJWT);\n// console.log('HOLA:', result.hola);\n```\n\n### Python Complete Implementation\n\n```python\nimport requests\nimport json\nfrom nacl.signing import SigningKey\nimport base58\nimport base64\n# 1. Get fresh nonce for this run\nnonce_response = requests.get(\nheaders = {'Authorization': f'Bearer {jwt_token}'}\n)\nnonce_data = nonce_response.json()\nnoncets_hex = nonce_data['noncetsHex']\ntimestamp = nonce_data['timestamp']\n# Example: { \"noncetsHex\": \"A1B2C3D4E5F6...\", \"timestamp\": \"2026-05-04T10:09:00.000Z\" }\n# 2. Load your NEAR credentials\ncreds = json.load(f)\nprivate_key_base58 = creds['private_key'].replace('ed25519:', '')\nkeypair = base58.b58decode(private_key_base58)\nsecret_key = keypair[:32]\n# 3. Build message\nrecipient = 'MUNDO'\nmessage = f'HOLA/{recipient}/{token_id}/{timestamp}/{noncets_hex}/API.IDENTYCLAW.COM/'\n# 4. Canonicalize (UPPERCASE)\ncanonical_message = message.upper()\n# 5. Sign\nsigning_key = SigningKey(secret_key)\nsignature = signing_key.sign(canonical_message.encode('utf-8')).signature\nsignature_b32 = base64.b32encode(signature).decode('utf-8').rstrip('=').upper()\n# 6. Calculate checksum\nchecksum_prefix = f'{canonical_message}{signature_b32}/'\nchar_sum = sum(ord(c) for c in checksum_prefix)\nhola_checksum_alphabet = 'ABCDEFGHJKMNPQRSTUVWXYZ'\nchecksum = hola_checksum_alphabet[char_sum % 23]\n# 7. Build final HOLA\nhola = f'{canonical_message}{signature_b32}/{checksum}'\n# Usage:\n# result = generate_hola('bjbvcjzqbdsj', your_jwt)\n# print('HOLA:', result['hola'])\n```\n\n**Key Points**:\n- ✓ Fetches **fresh nonce** from API (not hardcoded)\n- ✓ Uses **current timestamp** from nonce response\n- ✓ **Uppercases** entire message before signing\n- ✓ Returns complete HOLA ready to send\n- ✓ All values are **generated fresh** each time\n\n## Step 4: Send HOLA to Peer\n\nTransmit the HOLA message to the peer agent via your communication channel (HTTP, WebSocket, etc.).\n\nThe peer must apply **full validation** before treating your HOLA as authenticated — either directly (on-chain + local crypto) or via `POST /api/identity/verify` when using the IdentyClaw HTTP path ([When is a HOLA validated?](#when-is-a-hola-validated)).\n\n## Step 5: Verify HOLA (Peer Agent) — HTTP API path\n\nThis section documents **`POST /api/identity/verify`**, IdentyClaw's convenience implementation of full HOLA validation. If you verify peer-to-peer without the API, you must still apply the same substantive checks ([When is a HOLA validated?](#when-is-a-hola-validated)).\n\nWhen using this endpoint: if you only parse the string, recompute the checksum, or verify the Ed25519 signature locally without completing the full proof bar, you have **not** finished validation. **Treat a received HOLA as unvalidated until `POST /api/identity/verify` returns a successful verification outcome** (or until equivalent direct verification succeeds).\n\n**Endpoint**: `POST /api/identity/verify` (public; optional JWT for `RECIPIENT_MISMATCH`)\n\n```bash\n# Use YOUR freshly generated HOLA, not this example\ncurl -X POST https://api.identyclaw.com/api/identity/verify \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"hola\": \"HOLA/MUNDO/BJBVCJZQBDSJ/2026-05-04T10:09:00.000Z/A1B2C3D4.../API.IDENTYCLAW.COM/MFRGG.../J\"}'\n# Optional: -H \"Authorization: Bearer YOUR_JWT\"\n```\n\n**Success Response Pattern**:\n```json\n{\n  \"verified\": true,\n  \"peerTokenId\": \"bjbvcjzqbdsj\",\n  \"destinatary\": \"MUNDO\",\n  \"checks\": {\n    \"tokenExists\": true,\n    \"tokenActive\": true,\n    \"timestampFresh\": true,\n    \"nonceReplaySafe\": true,\n    \"signatureValid\": true,\n    \"checksumValid\": true\n  },\n  \"failureReasons\": [],\n  \"signatureVerificationImplemented\": true,\n  \"requestId\": \"01HX...\"\n}\n```\n\n## Verify Your HOLA Works\n\n**Test Endpoint**: `POST /api/testhola` (requires JWT)\n\nUse this endpoint to validate **your own** HOLA generation and iterate on mistakes before sending to peers. **Do not treat another agent’s HOLA as trusted because you ran equivalent local checks**—or because `/api/testhola` passed for your copy of their message—without completing [`POST /api/identity/verify`](#step-5-verify-hola-peer-agent--http-api-path) in the peer's trust workflow ([When is a HOLA validated?](#when-is-a-hola-validated)).\n\n```bash\n# Generate fresh HOLA using the pattern above, then test it:\ncurl -X POST https://api.identyclaw.com/api/testhola \\\n  -H \"Authorization: Bearer YOUR_JWT\" \\\n  -H \"Content-Type: application/json\" \\\n  \"hola\": \"YOUR_FRESHLY_GENERATED_HOLA\"\n```\n\n**Success Response** (200 OK):\n```json\n{\n  \"valid\": true,\n  \"peerTokenId\": \"bjbvcjzqbdsj\",\n  \"destinatary\": \"MUNDO\",\n  \"peerVerified\": true,\n  \"hola\": \"HOLA/MUNDO/...\",\n  \"serverTokenId\": \"...\",\n  \"checks\": {\n    \"formatValid\": true,\n    \"checksumValid\": true,\n    \"timestampValid\": true,\n    \"timestampFresh\": true,\n    \"noncetsValid\": true,\n    \"nonceReplaySafe\": true,\n    \"tokenExists\": true,\n    \"tokenActive\": true,\n    \"signatureValid\": true\n  }\n}\n```\n\n✓ **If you see `\"valid\": true` and all checks passing, your HOLA generation is working correctly!**\n\n**Important**:\n- Must use `Content-Type: application/json` header\n- Must include valid JWT in Authorization header\n- HOLA must be freshly generated (not from documentation examples)\n- Server responds with its own HOLA in the `hola` field\n- `/api/testhola` is JWT-protected as an API endpoint, but validation is based on the HOLA payload and cryptographic checks (not caller-token equality).\n- A successful response here means **your** build of the HOLA is sound; it does **not** replace `POST /api/identity/verify` when deciding whether to trust **another agent’s** HOLA ([When is a HOLA validated?](#when-is-a-hola-validated))\n\n## Common Pitfalls\n\n❌ **Treating checksum / signature as “good enough” without `/api/identity/verify`**\n- **Problem**: Accepting a peer’s HOLA for authorization after local checksum or Ed25519 verification only\n- **Solution**: Treat inbound HOLAs as **unvalidated** until `POST /api/identity/verify` succeeds; local crypto checks are at most a development aid ([When is a HOLA validated?](#when-is-a-hola-validated))\n\n❌ **Missing recipient field**\n- **Problem**: Signing `HOLA/<tokenId>/...` without recipient\n- **Solution**: Include recipient (default: `MUNDO`): `HOLA/MUNDO/<tokenId>/...`\n\n❌ **Using wrong nonce**\n- **Problem**: Using nonce from `/api/login/timestamp` (32 bytes) instead of `/api/holanonce16ts` (16 bytes)\n- **Solution**: Always use `/api/holanonce16ts` for HOLA messages\n\n❌ **Wrong checksum calculation**\n- **Problem**: Using cryptographic hash instead of simple sum\n- **Solution**: Sum UTF-16 code units (JavaScript `charCodeAt`) over `canonicalMessage + signature + \"/\"`, modulo 23, index into `ABCDEFGHJKMNPQRSTUVWXYZ`\n\n❌ **Not uppercasing before signing**\n- **Problem**: Signing the original mixed-case message instead of canonical uppercase\n- **Solution**: Call `.toUpperCase()` on the entire message before signing\n\n❌ **Missing trailing slash in checksum**\n- **Problem**: Checksumming `canonicalMessage + signature` without the trailing `/`\n- **Solution**: Include trailing slash: `checksumPrefix = canonicalMessage + signature + \"/\"`\n\n❌ **Wrong signature encoding**\n- **Problem**: Using base64url instead of **base32** for the HOLA line signature\n- **Solution**: Use base32 (RFC 4648, A-Z2-7) for HOLA signatures\n\n❌ **Expired nonce**\n- **Problem**: Using nonce that's too old\n- **Solution**: Request fresh nonce from `/api/holanonce16ts` before each HOLA\n\n## Debugging HOLA Failures\n\n### Step-by-Step Debugging Process\n\n**1. Verify Canonicalization**\n```json\nconsole.log('Original:', message),\nconsole.log('Canonical:', canonicalMessage),\nconsole.log('Match:', message === canonicalMessage),\n```\n\n**2. Verify Signature Encoding**\n```json\nconsole.log('Signature length:', signatureB32.length),\nconsole.log('Signature format valid:', /^[A-Z2-7]+$/.test(signatureB32)),\n```\n\n**3. Verify Checksum Calculation**\n```javascript\nconst checksumPrefix = `${canonicalMessage}${signatureB32}/`;\nconst sum = 0;\nsum += checksumPrefix.charCodeAt(i);\nconst holaChecksumAlphabet = 'ABCDEFGHJKMNPQRSTUVWXYZ';\nconst expectedChecksum = holaChecksumAlphabet[sum % 23];\n  console.log('Checksum prefix:', checksumPrefix)\n  console.log('Sum:', sum)\n  console.log('Expected:', expectedChecksum, 'Got:', checksum)\n// Should match\n```\n\n### Common Error Patterns\n\n| Error Code | Root Cause | Fix |\n|------------|------------|-----|\n| `HOLA_VALIDATION_FAILED` | Invalid format/payload/checksum | Check `error.details.stage` + `error.details.reasonCode` |\n| `HOLA_VALIDATION_FAILED` + `nonce_replay` | Nonce reused within validity window | Request fresh nonce from `/api/holanonce16ts` for each HOLA |\n| `HOLA_TIMESTAMP_INVALID` | HOLA line timestamp not valid ISO, or outside freshness window on `/api/testhola` | Use ISO from `GET /api/holanonce16ts` for that line; regenerate HOLA after a fresh nonce |\n| `HOLA_SIGNATURE_INVALID` + `public_key_unavailable` | Public key unavailable from blockchain | Verify token owner key availability / RPC health |\n| `HOLA_SIGNATURE_INVALID` + `signature_mismatch` | Wrong key pair or canonicalization/signing bug | Sign canonical uppercase message with correct owner key |\n| `HOLA_SIGNATURE_INVALID` + `token_expired` | Passport expired (`not_after`) | Renew/reissue passport |\n| `payload_is_json_object` | `hola` sent as JSON object instead of one string | Use `{\"hola\":\"HOLA/MUNDO/...\"}` only; keep envelope fields outside `hola` |\n| `unix_millis_timestamp` | Timestamp field is raw Unix ms | Use ISO 8601 from `/api/holanonce16ts` |\n| `signature_not_base32` | Signature uses base64url or other alphabet | HOLA line signature must be **base32** (not base64url) |\n| `placeholder_signature` | Brackets or prose in signature slot | Sign canonical uppercase prefix; emit real base32 |\n\n### Structured Failure Diagnostics (`/api/testhola`)\n\n`/api/testhola` rejects with machine-readable details in `error.details`:\n\n- `stage`: failing validation stage\n- `reasonCode`: stable classifier for tests/automation\n\nTypical values:\n- `format_checksum_and_payload_validation` + `invalid_format` / `checksum_invalid`\n- `timestamp_freshness_validation` + `timestamp_stale_or_future`\n- `nonce_replay_validation` + `nonce_replay`\n- `signature_verification` + `token_not_found` / `token_expired` / `public_key_unavailable` / `signature_mismatch` / `blockchain_unavailable_or_validation_error`\n\n### Verification Result Diagnostics (`/api/identity/verify`)\n\n`/api/identity/verify` always returns a verification outcome payload (HTTP 200 when request is well-formed), with:\n\n- `verified`: overall decision\n- `checks`: boolean stage results\n- `failureReasons`: machine-readable reasons (e.g. `checksum_invalid`, `timestamp_stale_or_future`, `nonce_replay`, `token_missing`, `token_expired`, `signature_invalid`, `public_key_unavailable`, `public_key_error`)\n\n## Next Steps\n\n- [API Login Authentication](login-authentication.md)\n- [Test with /api/testhola](https://api.identyclaw.com/openapi.json) — OpenAPI contract (`POST /api/testhola`)\n- [Understand Passport metadata](token-metadata.md)\n- [Explore API endpoints](api-reference.md)\n- [View JSON-LD integration](jsonld-metadata.md)\n","requestId":"d5d69586878ea15243a813ef832b403c"}