{"type":"text/markdown","content":"# Subagent HOLA Protocol\n\nComplete guide to the HOLA protocol for subagents using delegated authorization from parent agents.\n\n## Table of Contents\n\n- [Overview](#overview)\n- [When is subagent HOLA validated?](#when-is-subagent-hola-validated)\n- [When to Use](#when-to-use)\n- [Prerequisites](#prerequisites)\n- [Subagent HOLA Message Format](#subagent-hola-message-format)\n- [Two-Signature Model](#two-signature-model)\n- [Delegation Authorization](#delegation-authorization)\n- [Step 1: Generate NEAR Account](#step-1-generate-near-account)\n- [Step 2: Get JWT Token](#step-2-get-jwt-token)\n- [Step 3: Parent Creates Delegation Authorization](#step-3-parent-creates-delegation-authorization)\n- [Step 4: Request Nonce](#step-4-request-nonce)\n- [Step 5: Construct Subagent HOLA](#step-5-construct-subagent-hola)\n- [Step 6: Send HOLA to Peer](#step-6-send-hola-to-peer)\n- [Step 7: Verify Subagent HOLA (Peer Agent)](#step-7-verify-subagent-hola-peer-agent)\n- [Test Your Subagent HOLA](#test-your-subagent-hola)\n- [Critical Encoding Notes](#critical-encoding-notes)\n- [Common Pitfalls](#common-pitfalls)\n- [Troubleshooting](#troubleshooting)\n- [Next Steps](#next-steps)\n\n## Overview\n\nSubagent HOLA is an extension of the standard HOLA protocol that includes delegation information. Unlike standard agents who sign HOLA messages with their own IdentyClaw Passport, subagents use a two-signature model:\n1. **Delegation Authorization Signature** - Parent authorizes the subagent\n2. **HOLA Message Signature** - Subagent signs the HOLA with their own key\n\nThis enables parent agents to delegate authentication authority to subagents while maintaining a verifiable trust chain.\n\n### When is subagent HOLA validated?\n\nFollow the same rule as [standard HOLA](hola-agent-authentication.md#when-is-a-hola-validated): **checksum or local signature verification alone does not mean the HOLA is validated.** For subagents you must also complete **delegation** checks.\n\n**Full subagent validation** requires:\n\n1. **HOLA proof bar** — same as standard HOLA (direct or via `POST /api/identity/verify`)\n2. **Delegation authorization** — parent signed `{tokenId}:{delegateId}:{timestamp}:{publicKey}`; verify via direct cryptography against the parent's on-chain key **or** via `POST /api/isauthorizedsigner` (HTTP convenience path)\n\n**When using the IdentyClaw HTTP path:** treat an inbound subagent HOLA as unvalidated until **both** HTTP checks succeed:\n\n1. `POST /api/identity/verify` — full HOLA verification on the server  \n2. `POST /api/isauthorizedsigner` — delegation authorization (see [Step 7](#step-7-verify-subagent-hola-peer-agent))\n\nUntil full validation succeeds (by either path), do not grant trust, access, or elevated behavior. `POST /api/testhola` does not verify delegation authorization and must not be treated as full validation of another agent's subagent HOLA.\n\nConceptual background: [`public/policies/why-identyclaw.md`](../public/policies/why-identyclaw.md) §8.1 and §11.\n\n## When to Use\n\nUse Subagent HOLA Protocol when you need to:\n- Act as a subagent delegated by a parent agent\n- Prove your identity with delegated authorization\n- Implement hierarchical agent systems with controlled delegation\n- Enable subagent-to-agent communication with parent vouching\n- **You have delegation authorization from a parent agent**\n\n## Prerequisites\n\nBefore using Subagent HOLA, you must:\n\n1. **Have delegation authorization** - Parent agent must authorize your public key\n2. **Generate your NEAR account** - Use `gennearaccount` (same as standard agents)\n3. **Have a valid JWT token** - Complete API Login with your subagent account\n4. **Know your delegateID** - From parent's delegation authorization\n5. **Know parent's tokenId** - The parent's IdentyClaw Passport ID\n\n## Subagent HOLA Message Format\n\n**Structure**:\n```\nHOLA/<recipient>/<delegateID>/<issuer_tokenId>/<publicKey>/<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| `delegateID` | Subagent ID from authorization (base64url hash or DID) | `aBcDeFgHiJkLmNoPqRsTuVwXyZ` |\n| `issuer_tokenId` | Parent's Passport ID (12 lowercase letters) | `bkbvehbdcrgm` |\n| `publicKey` | Subagent's Ed25519 public key (base32-encoded) | `N3FZ5KQ8LH2BSM1XY...` |\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) | `MFRGG2LTMVZXGZLSN5XWC3TBNRQW4ZDJMQFA...` |\n| `checksum` | Single letter from `ABCDEFGHJKMNPQRSTUVWXYZ` (23 letters; omits **I**, **L**, **O**) | `J` |\n\n**Example Subagent HOLA Message**:\n```\nHOLA/MUNDO/aBcDeFgHiJkLmNoPqRsTuVwXyZ/bkbvehbdcrgm/N3FZ5KQ8LH2BSM1XY.../2026-04-19T10:47:00.000Z/4F9A3C7E2D1B9A4CDEADBEEFCAFEBABE/API.IDENTYCLAW.COM/dGVzdHNpZ25hdHVyZQ/a\n```\n\nSubagent HOLA uses the **same uppercase canonical signed payload and verifier normalization** as standard HOLA: sign and checksum the UTF-8 bytes of the prefix uppercased through `API.IDENTYCLAW.COM/`. **You do not need to transmit an all-uppercase line**; presentation on the wire is optional and stylistic choices that match your agent’s identity are welcome, as in standard HOLA. See [Canonical form vs on-the-wire appearance](hola-agent-authentication.md#canonical-form-vs-on-the-wire-appearance).\n\n## Two-Signature Model\n\nSubagents require two separate signatures for complete authentication:\n\n### 1. Delegation Authorization Signature\n- **Signed by**: Parent agent (Passport signing key)\n- **Message**: `{tokenId}:{delegateID}:{unixTimestamp}:{publicKey}`\n- **Verified via**: `POST /api/isauthorizedsigner`\n- **Purpose**: Proves parent authorized this subagent\n\n### 2. HOLA Message Signature\n- **Signed by**: Subagent (using own private key from `~/.near-credentials`)\n- **Message**: `HOLA/<recipient>/<delegateID>/<issuer_tokenId>/<publicKey>/<timestamp>/<noncets>/API.IDENTYCLAW.COM/`\n- **Verified via**: `POST /api/identity/verify`\n- **Purpose**: Proves subagent controls the authorized keypair\n\n**Requirement**: Both signatures must be valid for complete subagent authentication.\n\n## Delegation Authorization\n\nBefore generating HOLA messages, the parent must authorize the subagent.\n\n### Authorization Message Format\n\n```\n{tokenId}:{delegateID}:{unixTimestamp}:{publicKey}\n```\n\n**Components**:\n- `tokenId`: Parent's Passport ID (12 letters)\n- `delegateID`: Subagent ID (DID, name, or BLAKE3 hash of metadata)\n- `unixTimestamp`: When authorization was granted (Unix seconds)\n- `publicKey`: Subagent Ed25519 public key (base64url-encoded)\n\n### Subagent Metadata Template (for BLAKE3 hash)\n\nWhen using BLAKE3 hash for `delegateID`, use this metadata structure:\n\n```json\n{\n  \"schema\": \"openclaw.identity_meta.v1\",\n  \"context\": \"urn:openclaw:identity:2026\",\n  \"id\": \"did:openclaw:subagent123:session456:msg789\",\n  \"agent\": {\n    \"did\": \"did:openclaw:subagent123\",\n    \"publicKey\": \"base64url-encoded-subagent-public-key\",\n    \"type\": \"subagent\"\n  },\n  \"metadata\": {\n    \"expiresAt\": \"2027-12-31T23:59:59Z\",\n    \"permissions\": [\n      \"sign_hola\",\n      \"read_data\"\n    ],\n    \"scope\": \"customer-service\"\n  }\n}\n```\n\n### BLAKE3 Hash Function\n\n```javascript\nconst { createHash } = require(\"blake3\");\n\nfunction toBase64Url(buffer) {\n  return Buffer.from(buffer)\n    .toString(\"base64\")\n    .replace(/\\+/g, \"-\")\n    .replace(/\\//g, \"_\")\n    .replace(/=+$/, \"\");\n}\n\nfunction canonicalize(value) {\n  if (value === null || typeof value !== \"object\") {\n    return JSON.stringify(value);\n  }\n  if (Array.isArray(value)) {\n    return \"[\" + value.map(canonicalize).join(\",\") + \"]\";\n  }\n  const keys = Object.keys(value).sort();\n  const entries = keys.map((key) => JSON.stringify(key) + \":\" + canonicalize(value[key]));\n  return \"{\" + entries.join(\",\") + \"}\";\n}\n\nfunction canonicalizeAndHash(metadataJson) {\n  const canonicalJson = canonicalize(metadataJson);\n  const hashBuffer = createHash().update(canonicalJson).digest();\n  const hashOrDelegateId = toBase64Url(hashBuffer);\n  return { canonicalJson, hashOrDelegateId };\n}\n```\n\n**Notes**:\n- Keep field names and value casing stable - any change produces a new hash\n- Include stable identity fields in signed metadata so the delegation hash remains consistent across runs\n- Parent signs: `{tokenId}:{hashOrDelegateId}:{unixTimestamp}:{publicKey}`\n\n## Step 1: Generate NEAR Account\n\nSubagent generates their own NEAR account using the same mechanism as standard agents. Store the resulting JSON on **non-volatile storage** with other secrets (see [`references/enrollment.md#near-credentials-storage-required`](enrollment.md#near-credentials-storage-required)).\n\n```javascript\nmkdir -p <credentials-dir> && chmod 700 <credentials-dir>;\ngennearaccount <credentials-dir>;\n// Back up credentials JSON to encrypted operator storage\n```\n\nThis creates:\n- 64-character hex account ID\n- Ed25519 keypair stored at `<credentials-dir>/<accountid>.json` (default on persistent `$HOME`: `~/.near-credentials/mainnet/<accountid>.json`)\n\n## Step 2: Get JWT Token\n\nAuthenticate with your subagent account to get a JWT token.\n\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\": \"YOUR_SUBAGENT_ACCOUNTID\", \"timestamp\": 1776622758, \"base64url_signature\": \"...\"}'\n```\n\n**Response**:\n```json\n{\n  \"jwt_token\": \"eyJhbGc...\",\n  \"requestId\": \"01HQXYZ...\"\n}\n```\n\n## Step 3: Parent Creates Delegation Authorization\n\nParent agent creates the delegation signature offline (no API call required).\n\n**Message to sign**:\n```\n{parentTokenId}:{delegateID}:{unixTimestamp}:{subagentPublicKey}\n```\n\n**Example**:\n```javascript\nconst nacl = require('tweetnacl');\nconst bs58 = require('bs58');\n// Parent's credentials\nconst parentPrivateKey = /* parent's Passport signing key */;\n// Subagent's public key (base64url-encoded)\nconst subagentPublicKey = 'base64url-encoded-subagent-public-key';\n// Authorization message\nconst message = `${parentTokenId}:${delegateID}:${unixTimestamp}:${subagentPublicKey}`;\n// Sign with parent's key\nconst messageBytes = new TextEncoder().encode(message);\nconst signature = nacl.sign.detached(messageBytes, parentPrivateKey);\nconst signatureBase64url = Buffer.from(signature).toString('base64').replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n// Store for subagent\nconst authorization = {\n  tokenId: parentTokenId,\n  delegateID: delegateID,\n  unixTimestamp: unixTimestamp,\n  publicKey: subagentPublicKey,\n  signature: signatureBase64url\n};\n```\n\nParent provides this authorization to the subagent.\n\n## Step 4: Request Nonce\n\n⚠️ **CRITICAL: Which Key Pair to Use**\n\nYou must use the private key of the **subagent's NEAR account** (not the parent's key).\n\n**Key Requirements**:\n1. Use the private key of the **subagent's NEAR account**\n2. Private key location: `~/.near-credentials/mainnet/<subagent_accountid>.json`\n3. The parent's key is only used for delegation authorization\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_SUBAGENT_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## Step 5: Construct Subagent HOLA\n\n**Message to sign** (everything before the signature field):\n```\nHOLA/<recipient>/<delegateID>/<issuer_tokenId>/<publicKey>/<timestamp>/<noncets-hex>/API.IDENTYCLAW.COM/\n```\n\n**Reference (IdentyClaw) — wire format** (your values will differ):\n```\nHOLA/MUNDO/aBcDeFgHiJkLmNoPqRsTuVwXyZ/bkbvehbdcrgm/N3FZ5KQ8LH2BSM1XY.../2026-04-19T10:47:00.000Z/4F9A3C7E2D1B9A4CDEADBEEFCAFEBABE/API.IDENTYCLAW.COM/\n```\n\n### Step 5.1: Canonicalization (CRITICAL)\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. **delegateID** → `ABCDEF...` (uppercase the base64url string)\n3. **issuer_tokenId** → `BKBVEHBDCRGM` (even though stored as lowercase)\n4. **publicKey** → `N3FZ5KQ8LH2BSM1XY...` (uppercase the base32 string)\n5. **timestamp** → `2026-04-19T10:47:00.000Z` (verify 'Z' is uppercase)\n6. **noncetsHex** → `4F9A3C7E2D1B9A4CDEADBEEFCAFEBABE` (already uppercase from API)\n7. **domain** → `API.IDENTYCLAW.COM` (already uppercase)\n\n**Reference (IdentyClaw) — wire format**:\n```javascript\n// Original message components (use YOUR fresh values)\nconst recipient = 'MUNDO';\nconst delegateID = 'aBcDeFgHiJkLmNoPqRsTuVwXyZ';  // From authorization;\nconst issuerTokenId = 'bkbvehbdcrgm';  // Parent's passport ID;\nconst publicKey = 'n3fz5kq8lh2bsm1xy...';  // Your public key (base32);\nconst timestamp = '2026-04-19T10:47:00.000Z';  // From Step 4;\nconst noncetsHex = '4F9A3C7E2D1B9A4CDEADBEEFCAFEBABE';  // From Step 4;\n// Build message\nconst message = `HOLA/${recipient}/${delegateID}/${issuerTokenId}/${publicKey}/${timestamp}/${noncetsHex}/API.IDENTYCLAW.COM/`;\n// Canonicalize: UPPERCASE EVERYTHING\nconst canonicalMessage = message.toUpperCase();\n// Sign the canonical message\nconst messageBytes = new TextEncoder().encode(canonicalMessage);\nconst signature = nacl.sign.detached(messageBytes, subagentSecretKey);\n```\n\n### Step 5.2: Sign the Canonical Message\n\n**Signing**:\n1. Convert **canonical (uppercase)** message to UTF-8 bytes\n2. Sign with **subagent's** Ed25519 secret key (from `~/.near-credentials`)\n3. Encode signature as **base32** (RFC 4648, A-Z2-7, uppercase, no padding)\n\n**Reference (IdentyClaw) — JavaScript wire format**:\n```javascript\nconst nacl = require('tweetnacl');\nconst base32 = require('hi-base32');\nconst bs58 = require('bs58');\nconst fs = require('fs');\n// 1. Load subagent credentials\nconst creds = JSON.parse(fs.readFileSync('~/.near-credentials/mainnet/subagent-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 values)\nconst message = `HOLA/MUNDO/aBcDeFgHiJkLmNoPqRsTuVwXyZ/bkbvehbdcrgm/N3FZ5KQ8LH2BSM1XY.../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```\n\n### Step 5.3: Checksum Calculation\n\n**Algorithm**: `checksumPrefix = canonicalUppercaseThroughApiDotCom + base32Signature + '/'`; `checksum = ABCDEFGHJKMNPQRSTUVWXYZ[(sum of UTF-16 code units over checksumPrefix) % 23]`.\n\nThe checksum is calculated on the **canonicalized (uppercase) message + signature + trailing slash**.\n\n```javascript\n// Step 1: Build canonical message (already uppercase)\nconst canonicalMessage = message.toUpperCase();\n// Step 2: Add signature and trailing slash\nconst checksumPrefix = `${canonicalMessage}${signatureB32}/`;\n// Step 3: Sum ASCII character codes\nconst sum = 0;\nsum += checksumPrefix.charCodeAt(i);\n// Step 4: Modulo 16 and convert to hex\nconst holaChecksumAlphabet = 'ABCDEFGHJKMNPQRSTUVWXYZ';\nconst checksum = holaChecksumAlphabet[sum % 23];\n```\n\n**Final Subagent HOLA Pattern**:\n```\nHOLA/MUNDO/ABCDEF.../BKBVEHBDCRGM/N3FZ5KQ8LH2BSM1XY.../2026-04-19T10:47:00.000Z/4F9A3C7E.../API.IDENTYCLAW.COM/MFRGG2LTMVZXGZLSN5XWC3TBNRQW4ZDJMQFA.../F\n```\n\n## Step 6: Send HOLA to Peer\n\nTransmit the subagent HOLA message to the peer agent via your communication channel (HTTP, WebSocket, etc.).\n\nThe peer agent will verify your HOLA using both `/api/identity/verify` and `/api/isauthorizedsigner`. **Neither you nor the peer should treat the message as validated until those server calls succeed** ([When is subagent HOLA validated?](#when-is-subagent-hola-validated)).\n\n## Step 7: Verify Subagent HOLA (Peer Agent)\n\nPeer agent must verify both signatures to establish trust. **Local checksum or HOLA signature checks are not sufficient**—full validation requires the API steps below ([When is subagent HOLA validated?](#when-is-subagent-hola-validated)).\n\n### Step 7.1: Verify HOLA Signature\n\n**Endpoint**: `POST /api/identity/verify` (public; optional JWT for `RECIPIENT_MISMATCH`)\n\n```bash\ncurl -X POST https://api.identyclaw.com/api/identity/verify \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"hola\": \"HOLA/MUNDO/ABCDEF.../BKBVEHBDCRGM/N3FZ5KQ8LH2BSM1XY.../2026-04-19T10:47:00.000Z/4F9A3C7E.../API.IDENTYCLAW.COM/MFRGG.../F\"}'\n# Optional: -H \"Authorization: Bearer YOUR_JWT\"\n```\n\n**Success Response Pattern**:\n```json\n{\n  \"verified\": true,\n  \"peerTokenId\": \"delegateID\",\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  \"requestId\": \"01HX...\"\n}\n```\n\n### Step 7.2: Verify Delegation Authorization\n\n**Endpoint**: `POST /api/isauthorizedsigner` (requires JWT)\n\n```bash\ncurl -X POST https://api.identyclaw.com/api/isauthorizedsigner \\\n  -H \"Authorization: Bearer YOUR_JWT\" \\\n  -H \"Content-Type: application/json\" \\\n  \"tokenId\": \"bkbvehbdcrgm\", \\\n  \"hashOrDelegateId\": \"aBcDeFgHiJkLmNoPqRsTuVwXyZ\", \\\n  \"unixTimestamp\": 1714143000, \\\n  \"publicKey\": \"base64url-encoded-subagent-public-key\", \\\n  \"signature\": \"parent-authorization-signature\"\n```\n\n**Success Response Pattern**:\n```json\n{\n  \"authorized\": true,\n  \"checks\": {\n    \"signatureValid\": true,\n    \"timestampValid\": true,\n    \"publicKeyMatch\": true\n  },\n  \"requestId\": \"01HX...\"\n}\n```\n\nBoth verifications must pass for complete subagent authentication.\n\n## Test Your Subagent HOLA\n\n**Test Endpoint**: `POST /api/testhola` (requires JWT)\n\nUse this endpoint to validate **your own** subagent HOLA generation before sending to peers. It does **not** check delegation authorization and does **not** replace `POST /api/identity/verify` plus `POST /api/isauthorizedsigner` when deciding whether to trust **another agent’s** subagent HOLA ([When is subagent HOLA validated?](#when-is-subagent-hola-validated)).\n\n```bash\n# Generate fresh subagent HOLA using the pattern above, then test it:\ncurl -X POST https://api.identyclaw.com/api/testhola \\\n  -H \"Authorization: Bearer YOUR_SUBAGENT_JWT\" \\\n  -H \"Content-Type: application/json\" \\\n  \"hola\": \"YOUR_FRESHLY_GENERATED_SUBAGENT_HOLA\"\n```\n\n**Note**: Authenticate with a valid JWT to call `/api/testhola`. Validation outcomes are based on HOLA fields and cryptographic checks, not enforced JWT sender-token matching.\n\n**Success Response** (200 OK):\n```json\n{\n  \"valid\": true,\n  \"peerTokenId\": \"delegateID\",\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**Note**: `/api/testhola` validates format, signature, and checksum but does NOT verify delegation authorization - use `/api/isauthorizedsigner` separately for that.\n\n## Critical Encoding Notes\n\n**Canonical vs wire (case):** Subagent HOLA follows the same rules as standard HOLA: verifiers normalize casing when reconstructing the signed prefix. Capitalization on the wire is **not** required; creative presentation is welcome. Cryptography must still use the uppercase canonical body through `API.IDENTYCLAW.COM/`. Details: [Canonical form vs on-the-wire appearance](hola-agent-authentication.md#canonical-form-vs-on-the-wire-appearance).\n\nDifferent endpoints use different encodings:\n\n- **HOLA signatures**: Always **base32** (RFC 4648 Base32: A-Z2-7, uppercase, no padding)\n- **Subagent public key in HOLA**: **base32** (52 characters, no padding)\n- **Delegation public key in authorization**: **base64url** (for `/api/isauthorizedsigner` endpoint)\n- **Login signatures**: **base64url** (for `/api/login` endpoint)\n\n**Encoding Summary**:\n| Context | Encoding | Purpose |\n|---------|----------|---------|\n| HOLA message signature | base32 | Ed25519 signature in HOLA protocol |\n| Public key in HOLA | base32 | Subagent public key in HOLA message |\n| Delegation signature | base64url | Parent signature for authorization |\n| Public key in authorization | base64url | Subagent public key for `/api/isauthorizedsigner` |\n| API login signature | base64url | Signature for `/api/login` |\n\n## Common Pitfalls\n\n❌ **Trusting checksum / local signature without `/api/identity/verify` (and delegation)**\n- **Problem**: Accepting a subagent HOLA after offline checks only\n- **Solution**: Treat as **unvalidated** until `POST /api/identity/verify` **and** `POST /api/isauthorizedsigner` both succeed ([When is subagent HOLA validated?](#when-is-subagent-hola-validated))\n\n❌ **Wrong signature key**\n- **Problem**: Subagent signs HOLA with parent's key instead of own key\n- **Solution**: Use subagent's private key from `~/.near-credentials/mainnet/<subagent-account>.json`\n\n❌ **Wrong HOLA format**\n- **Problem**: Using standard agent HOLA format (8 fields) instead of subagent format (11 fields)\n- **Solution**: Subagent format has 3 extra fields: `delegateID`, `issuer_tokenId`, `publicKey`\n\n❌ **Encoding confusion**\n- **Problem**: Using base64url instead of base32 for HOLA signature\n- **Solution**: HOLA signatures always use base32 (A-Z2-7, uppercase, no padding)\n\n❌ **Missing delegation verification**\n- **Problem**: Only verifying HOLA signature, not delegation authorization\n- **Solution**: Peer must call both `/api/identity/verify` and `/api/isauthorizedsigner`\n\n❌ **Reused nonce**\n- **Problem**: Using stale nonce from previous HOLA\n- **Solution**: Request fresh nonce from `/api/holanonce16ts` for each HOLA\n\n## Troubleshooting\n\n### signature_invalid (subagent HOLA)\n**Cause**: Subagent signed with wrong key (parent's instead of own)\n\n**Solutions**:\n- Verify subagent uses its OWN private key for HOLA signature\n- Parent's key is only used for delegation signature (`/api/isauthorizedsigner`)\n- Check signed message format matches subagent format exactly\n\n### HOLA_VALIDATION_FAILED (wrong field count / format)\n**Cause**: Wrong number of fields in HOLA message (`error.details.reasonCode` often reflects format parsing)\n\n**Solutions**:\n- Subagent format: 11 fields total (3 extra: delegateID, issuer_tokenId, publicKey)\n- Count separators `/` - should be 10 for subagent\n- Verify field order matches specification exactly\n\n### HOLA_VALIDATION_FAILED (delegate id)\n**Cause**: delegateID is missing or wrong length\n\n**Solutions**:\n- delegateID must be 1-128 characters\n- Use descriptive IDs: `'researcher-sub-001'` not `'abc'`\n- delegateID must match what was used in `/api/isauthorizedsigner`\n\n### subagent_public_key_invalid_length\n**Cause**: Public key in HOLA is not 32 bytes when decoded\n\n**Solutions**:\n- Public key must be base32-encoded Ed25519 key (32 bytes)\n- Encoded length should be 52 characters (no padding)\n- Verify decoded length is exactly 32 bytes\n\n### publicKeyAuthorizationFailed (/api/isauthorizedsigner)\n**Cause**: Parent signature is invalid or timestamp mismatch\n\n**Solutions**:\n- Verify parent signed correct message: `tokenId:delegateId:timestamp:publicKey`\n- Ensure timestamp matches delegation record exactly\n- Check parent's private key is correct\n- Verify signature is base64url-encoded (no padding)\n\n## Next Steps\n\n- [Standard Agent HOLA](hola-agent-authentication.md) - For non-delegated agent authentication\n- [Subagent HOLA guide](hola-subagent-authentication.md) and [holanonce API](holanonce-api.md) — delegation + nonce response\n- [API Login Authentication](login-authentication.md) - How to get JWT tokens\n- [Test with /api/testhola](https://api.identyclaw.com/openapi.json) — OpenAPI contract (`POST /api/testhola`)\n- [Explore API endpoints](api-reference.md) - Complete API reference\n","requestId":"1b2f36f6457e68fad56f1ca0a183f0c8"}