UNPKG

@agentauth/core

Version:

Core identity and cryptographic primitives for AgentAuth

568 lines (412 loc) β€’ 22 kB
# @agentauth/core: Core Identity and Cryptographic Primitives for AgentAuth ID [![npm version](https://img.shields.io/npm/v/@agentauth/core.svg)](https://www.npmjs.com/package/@agentauth/core) [![npm downloads](https://img.shields.io/npm/dm/@agentauth/core.svg)](https://www.npmjs.com/package/@agentauth/core) [![Types](https://img.shields.io/npm/types/@agentauth/core)](https://www.npmjs.com/package/@agentauth/core) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![GitHub stars](https://img.shields.io/github/stars/agentauthco/agentauth?style=social)](https://github.com/agentauthco/agentauth) Core identity primitive and cryptographic components for **AgentAuth ID** β€” the self-authenticating UUID for AI agents. This package provides the low-level cryptographic foundation used by the AgentAuth ecosystem. It handles key generation, address derivation, signing, and verification using secp256k1 elliptic curve cryptography. Learn more about AgentAuth at https://github.com/agentauthco/agentauth. ## Why @agentauth/core? - **πŸ”‘ Complete Identity System** β€” Generate stable agent identities with one function call - **πŸ” Industry-standard Cryptography** β€” Uses secp256k1 and battle-tested industry standards throughout - **πŸ†” Deterministic UUIDs** β€” Same private key always generates the same UUID - **✍️ Sign & Verify** β€” Simple payload signing and verification for authentication flows - **πŸ›‘οΈ Minimal, Well-Audited Dependencies** β€” Built on Noble cryptographic libraries, UUID, and nothing else ## Installation ```bash npm install @agentauth/core ``` ## Quick Start ### Generate a New Identity Create a complete AgentAuth identity with one function call: ```typescript import { generateIdentity } from '@agentauth/core'; const identity = generateIdentity(); console.log(identity); // { // agentauth_token: 'aa-2337b9fa957a201db466a58065529dc40362e008d3f41655651b96b2abbcb602', // agentauth_address: '0x9906322508aa2d8cbf24c33751015162d58285ce', // agentauth_id: '811ec2bf-b653-573a-b2ea-6ff4df9fdad7' // } ``` The identity includes: - `agentauth_token`: The private key (with aa- prefix) β€” ⚠️ keep this secret! - `agentauth_address`: An industry-standard address derived from `agentauth_token`, used for verification - `agentauth_id`: A stable UUID v5 derived from `agentauth_address` > [!TIP] > As you can see, all you actually need to re-derive the full identity primitive is the `agentauth_token`, which makes it super lightweight! > [!IMPORTANT] > That's also why it's so important to protect it during usage (e.g. by only using it locally), and for users to store it **SECURELY**! ### Work with Existing Keys Derive address and ID from an existing private key: ```typescript import { deriveAddress, generateId } from '@agentauth/core'; // Accepts any format: aa-, 0x, or raw hex const privateKey = 'aa-2337b9fa957a201db466a58065529dc40362e008d3f41655651b96b2abbcb602'; const address = deriveAddress(privateKey); const id = generateId(address); console.log(address); // '0x9906322508aa2d8cbf24c33751015162d58285ce' console.log(id); // '811ec2bf-b653-573a-b2ea-6ff4df9fdad7' ``` ### Sign and Verify Payloads Use for authentication flows and message signing: ```typescript import { signPayload, verifySignature } from '@agentauth/core'; const payload = { timestamp: new Date().toISOString(), action: 'authenticate', data: { tool: 'weather-forecast' } }; // Sign with private key const signature = signPayload(payload, privateKey); // Verify with address const isValid = verifySignature(signature, payload, address); console.log(isValid); // true ``` ## Identity Primitive The AgentAuth identity primitive consists of three components that work together to provide a complete, self-contained identity system. Each component serves a specific purpose and was chosen for both technical and practical reasons. ### AgentAuth Token (`agentauth_token`) **What it is:** A secp256k1 private key with an `aa-` prefix, formatted as 64 characters of hex, used for signing and authentication. ``` Format: aa-[64 hex characters] Example: aa-2337b9fa957a201db466a58065529dc40362e008d3f41655651b96b2abbcb602 ``` **Why we chose this design:** - **Battle-tested cryptography:** Uses the same secp256k1 curve and key derivation as Bitcoin and Ethereum, securing billions of dollars in value - **Industry standard:** Developers already understand this format and its security properties, with many well-audited, compatible libraries - **Self-contained:** The token alone is sufficient to derive the complete identity - **Recognizable prefix:** The `aa-` prefix makes AgentAuth tokens immediately identifiable and prevents accidental mixing with other key formats - **Future compatibility:** Uses Bitcoin/Ethereum-compatible formats, enabling integration with existing cryptographic tooling **Security properties:** - 256 bits of entropy (same security level as Bitcoin/Ethereum private keys) - Generated using cryptographically secure random number generation - Should be treated like any private key: keep secret, store securely We use [@noble/secp256k1](https://github.com/paulmillr/noble-secp256k1) for all cryptographic operations, chosen for its audit history and constant-time implementations. > [!NOTE] > While AgentAuth currently uses secp256k1 for its default identity system β€” chosen for its ecosystem maturity, developer familiarity, and audit-backed performance β€” future versions of AgentAuth will **also** support Ed25519-based tokens for broader cryptographic interoperability and enhanced performance characteristics. ### AgentAuth Address (`agentauth_address`) **What it is:** A cryptographically stable and secure address derived from the private key using keccak256 derivation, used for signature verification ``` Format: 0x[40 hex characters] Example: 0x9906322508aa2d8cbf24c33751015162d58285ce ``` **Why we chose this design:** - **Verification-ready:** Servers can verify signatures using the same battle-tested algorithms as Ethereum (ecrecover) - **Standard format:** 20-byte Ethereum/EVM-style addresses are well-understood by developers and ergonomic for users - **Tool compatibility:** Works with existing development tools and libraries for testing and debugging - **Efficient performance:** Enables efficient signature verification using proven ecrecover algorithms - **Future extensibility:** Uses Ethereum/EVM-compatible address format, enabling potential integrations without breaking changes **Role in the system:** - Used for cryptographic verification of signed payloads - Never sent aloneβ€”always accompanied by a signature proving ownership - Derived deterministically from the private key (same key = same address) The derivation follows Ethereum's address derivation: keccak256(publicKey)[12:] where publicKey is the uncompressed secp256k1 public key. ### AgentAuth ID (`agentauth_id`) **What it is:** A UUID v5 generated deterministically from the AgentAuth Address, used for stable identification across systems ``` Format: [UUID v5] Example: 811ec2bf-b653-573a-b2ea-6ff4df9fdad7 ``` **Why we chose this design:** - **Database-friendly:** UUIDs are a standard, well-supported identifier format - **Deterministic:** Same address always produces the same UUID - **Globally unique:** UUID v5 guarantees uniqueness across all systems - **Privacy-preserving:** Doesn't directly expose the cryptographic address - **Application-ready:** Perfect for use as primary keys, user IDs, and foreign keys **Role in the system:** - The stable, public identifier used by applications - Safe to store in databases, logs, and APIs - Enables user recognition across different MCP servers - Generated using a fixed namespace UUID to ensure consistency We use a fixed, custom namespace UUID (`2f5a5c48-c283-4231-8975-9271fe11e86c`) to derive all AgentAuth UUIDs to ensure they are all unique and stable. ## How AgentAuth Works Understanding how AgentAuth works with this library under the hood helps explain why it's both secure and simple. The system is built on four key processes that utilize this library, in conjunction with the identity primitive above. ### Identity Generation Process When you call `generateIdentity()`, here's what happens: **1. Secure randomness:** We generate 32 bytes (256 bits) of cryptographically secure random data using the platform's secure random number generator. **2. Private key formatting:** The random bytes are encoded as hex and prefixed with `aa-` to create the AgentAuth Token. **3. Deterministic derivation:** The address and UUID are derived using the processes below. ```typescript // Simplified conceptual flow const randomBytes = secureRandom(32); // 256 bits of entropy const privateKey = 'aa-' + toHex(randomBytes); // Format as AgentAuth Token const address = deriveAddress(privateKey); // Ethereum-style derivation const uuid = generateId(address); // UUID v5 generation ``` **Why this approach:** - **Maximum entropy:** Uses platform secure randomness for unpredictability - **No server dependency:** Identity generation works completely offline - **Deterministic rebuild:** The complete identity can be reconstructed from just the token ### Primitive Derivation Process The identity primitive's three components are mathematically related through a deterministic chain: ```mermaid flowchart A[AgentAuth Token<br/>Private Key - 32 bytes<br/>aa-2337b9fa...] -- secp256k1 + keccak256 --> B[AgentAuth Address<br/>Public Address - 20 bytes<br/>0x99063225...] B -- UUIDv5 + Namespace --> C[AgentAuth ID<br/>UUID v5<br/>811ec2bf-b653...] ``` **Token β†’ Address derivation:** 1. Remove `aa-` prefix and convert hex to bytes 2. Generate secp256k1 public key from private key ([handled by @noble/secp256k1](https://github.com/paulmillr/noble-secp256k1)) 3. Apply keccak256 hash to the uncompressed public key 4. Take the last 20 bytes and format with `0x` prefix **Address β†’ UUID derivation:** 1. Use the address as input to UUID v5 generation 2. Apply our fixed namespace UUID (`2f5a5c48-c283-4231-8975-9271fe11e86c`) 3. Generate deterministic UUID following [RFC 4122](https://tools.ietf.org/html/rfc4122) **Why deterministic derivation:** - **Reproducible:** Same token always generates the same address and UUID - **Stateless:** No need to store mappings between primitives - **Verifiable:** Anyone can verify the relationships between primitives ### Authentication Signature Process When an agent needs to authenticate (e.g., via @agentauth/mcp), here's the signature flow: **1. Payload construction:** Create a JSON object with timestamp ```typescript const payload = { timestamp: '2024-01-15T10:30:00.000Z' }; ``` **2. Canonical JSON:** Convert to deterministic string representation ```typescript const message = JSON.stringify(payload); // Must be deterministic ``` **3. Signature generation:** Sign the message using ECDSA - Hash the message with keccak256 - Sign the hash using secp256k1 ECDSA ([handled by @noble/secp256k1](https://github.com/paulmillr/noble-secp256k1)) - Format as hex with recovery bit **4. Header transmission:** Send via HTTP headers - `x-agentauth-address`: The AgentAuth Address - `x-agentauth-signature`: The signature with `0x` prefix - `x-agentauth-payload`: Base64-encoded JSON payload **Why this design:** - **Standard cryptography:** Uses well-understood ECDSA signatures - **Stateless replay protection:** Timestamp provides replay protection without requiring stateful nonce storage (e.g. by the server) - **Efficient verification:** Verify without storing state or tracking used nonces - **HTTP compatible:** Works with standard HTTP header mechanisms **Note on nonces:** We deliberately chose timestamp-based replay protection over nonces to maintain statelessness: - Nonces would require servers to track used values, adding complexity and storage requirements - A freshness window provides adequate replay protection for most use cases while keeping verification completely stateless - Future versions of @agentauth/mcp and @agentauth/sdk may add optional nonce support for applications requiring additional stateful security measures - It's also worth noting that the @agentauth/core library itself is designed for maximum flexibility and is **unopinionated** about the payloads it signs **Industry precedent:** Timestamp-based authentication windows are widely used across the industry: - **AWS Signature V4:** 15-minute default window for signed requests - **Google Cloud APIs:** 15-minute window for OAuth token timestamps - **JWT tokens:** Configurable `exp` and `nbf` claims for time-based validation - **OAuth 2.0:** Timestamp validation in bearer token flows - **HMAC-based APIs:** Common pattern with 5-15 minute windows Our 60-second default strikes a balance between security (shorter than most) and practical network/clock tolerance. ### Server Verification Process When an MCP server receives a request (e.g., via @agentauth/sdk), here's the verification flow: **1. Header extraction:** Parse the three AgentAuth headers ```typescript const address = headers['x-agentauth-address']; // 0x99063225... const signature = headers['x-agentauth-signature']; // 0x1234abcd... const payload = base64Decode(headers['x-agentauth-payload']); ``` **2. Signature verification:** Verify the signature matches the payload and address - Decode the base64 payload back to JSON - Hash the JSON string with keccak256 - Use ecrecover to extract the signing address from the signature - Compare recovered address with provided address **3. Freshness check:** Ensure the request is recent ```typescript const payloadObj = JSON.parse(payload); const timestamp = new Date(payloadObj.timestamp); const now = new Date(); const isRecent = (now - timestamp) < 60000; // 60 second default window ``` **Configurable freshness window:** The 60-second window is configurable via the `VerifyOptions` parameter: ```typescript // Default 60-second window const result = verify({ headers }); // Custom 2-minute window for high-latency networks const result = verify({ headers }, { freshness: 120000 }); // Stricter 30-second window for high-security applications const result = verify({ headers }, { freshness: 30000 }); ``` This flexibility allows applications to adjust based on their network conditions, security requirements, and clock synchronization tolerance. Most applications can use the 60-second default, which is more conservative than industry standards (AWS: 15 minutes, Google: 15 minutes) while accommodating typical network latency and clock drift. **4. Identity extraction:** Generate the AgentAuth ID ```typescript if (signatureValid && isRecent) { const agentId = generateId(address); // Deterministic UUID generation // Agent authenticated! Use agentId as user identifier } ``` **Why this approach:** - **Stateless verification:** No server-side session storage needed - **Standard cryptography:** Uses proven signature verification techniques - **Replay protection:** Timestamp validation prevents old requests - **Immediate identity:** Can extract stable UUID for database use ### Complete Authentication Flow Here's how all the pieces work together in a real authentication scenario: ```mermaid sequenceDiagram participant Agent participant MCP_Proxy as @agentauth/mcp participant MCP_Server as @agentauth/sdk participant App as MCP Server App Agent->>MCP_Proxy: Request with AGENTAUTH_TOKEN MCP_Proxy->>MCP_Proxy: Generate timestamp payload MCP_Proxy->>MCP_Proxy: Sign payload with AgentAuth Token MCP_Proxy->>MCP_Server: HTTP request + AgentAuth headers MCP_Server->>MCP_Server: Verify signature & freshness using AgentAuth Address MCP_Server->>MCP_Server: Extract AgentAuth ID MCP_Server->>App: provide(agentauth_id: "811ec2bf...") App->>App: Use UUID for database operations App->>MCP_Server: Response based on agent identity MCP_Server->>MCP_Proxy: HTTP response MCP_Proxy->>Agent: Tool response ``` This end-to-end flow ensures that: - Agents never send private keys over the network - Servers get a stable, verified identity for each agent - No centralized auth service is required - The system works entirely over standard HTTP ## API Reference ### `generateIdentity(algorithm?: Algorithm)` Generates a complete AgentAuth identity including private key, address, and deterministic ID. **Parameters:** - `algorithm`: Currently only supports `'secp256k1'` (default) **Returns:** ```typescript { agentauth_token: string; // Private key with aa- prefix agentauth_address: string; // Ethereum-compatible address with 0x prefix agentauth_id: string; // UUID v5 } ``` **Example:** ```typescript const identity = generateIdentity(); // Store identity.agentauth_token securely! // Use identity.agentauth_id as the public identifier ``` ### `deriveAddress(privateKey: string)` Derives an Ethereum-compatible address from a private key. **Parameters:** - `privateKey`: Private key in any format (aa-, 0x, or raw hex) **Returns:** - Ethereum-compatible address with 0x prefix (20 bytes) **Example:** ```typescript const address = deriveAddress('aa-2337...'); // '0x9906322508aa2d8cbf24c33751015162d58285ce' ``` ### `generateId(address: string)` Generates a deterministic UUID v5 from an address. **Parameters:** - `address`: Ethereum-compatible address (0x-prefixed) **Returns:** - UUID v5 string **Example:** ```typescript const id = generateId('0x9906322508aa2d8cbf24c33751015162d58285ce'); // '811ec2bf-b653-573a-b2ea-6ff4df9fdad7' ``` ### `signPayload(payload: object, privateKey: string)` Signs a JSON payload using a private key. **Parameters:** - `payload`: JSON object to sign - `privateKey`: Private key in any format **Returns:** - Hex-encoded signature with 0x prefix (65 bytes) **Example:** ```typescript const signature = signPayload({ msg: 'hello' }, privateKey); // '0x1234...abcd' ``` ### `verifySignature(signature: string, payload: object, expectedAddress: string)` Verifies a signature against a payload and expected address. **Parameters:** - `signature`: Hex-encoded signature with 0x prefix - `payload`: Original JSON payload - `expectedAddress`: Expected Ethereum-compatible address **Returns:** - Boolean indicating validity **Example:** ```typescript const isValid = verifySignature(signature, payload, address); // true ``` ### `parsePrivateKey(token: string)` Utility function to parse private keys from any supported format. **Parameters:** - `token`: Private key with aa-, 0x, or no prefix **Returns:** - Clean 32-byte hex string (64 characters) **Example:** ```typescript parsePrivateKey('aa-2337...'); // '2337...' parsePrivateKey('0x2337...'); // '2337...' parsePrivateKey('2337...'); // '2337...' ``` ## Common Patterns ### Identity Storage Store only what you need: ```typescript // For servers: Store the UUID as user identifier const identity = generateIdentity(); await db.users.create({ id: identity.agentauth_id, created: new Date() }); // For clients: Store the private key securely const identity = generateIdentity(); await secureStorage.set('agentauth_token', identity.agentauth_token); ``` ### Authentication Flow Build a simple authentication system: ```typescript // Client side: Sign authentication request const authPayload = { timestamp: new Date().toISOString() }; const signature = signPayload(authPayload, privateKey); const address = deriveAddress(privateKey); // Send to server: { address, signature, payload: authPayload } // Server side: Verify and extract ID const isValid = verifySignature(signature, authPayload, address); if (isValid) { const agentId = generateId(address); // Authenticated! Use agentId as user identifier } ``` ### Key Format Handling The library accepts multiple key formats for compatibility: ```typescript // All these are equivalent const key1 = 'aa-2337b9fa957a201db466a58065529dc40362e008d3f41655651b96b2abbcb602'; const key2 = '0x2337b9fa957a201db466a58065529dc40362e008d3f41655651b96b2abbcb602'; const key3 = '2337b9fa957a201db466a58065529dc40362e008d3f41655651b96b2abbcb602'; // All will produce the same address deriveAddress(key1) === deriveAddress(key2) === deriveAddress(key3); ``` ## Security Notes - **Private keys** (`agentauth_token`) should be kept secret and stored securely - **Deterministic generation** means same private key always produces same ID - **No random salts** in signatures β€” include timestamp in payload for replay protection - **Ethereum compatibility** allows integration with existing Ethereum development tools for testing and debugging - **Noble libraries** provide audited, constant-time implementations ## TypeScript Support Full TypeScript support with exported types: ```typescript import type { Algorithm } from '@agentauth/core'; // Currently only 'secp256k1' is supported const algo: Algorithm = 'secp256k1'; ``` ## Testing Run the test suite: ```bash npm test ``` ## Contributing AgentAuth ID is an early-stage open-source project maintained by the AgentAuth team. We welcome bug reports, feature suggestions, and early feedback via [GitHub Issues](https://github.com/agentauthco/agentauth/issues). You can also reach out at [developers@agentauth.co](mailto:developers@agentauth.co?subject=Contributing%20to%20AgentAuth) if you are interested in contributing. ## License MIT License β€” see [LICENSE](https://github.com/agentauthco/agentauth/blob/main/LICENSE) for details. ## Links - **Website** β€” [agentauth.co](https://agentauth.co) - **Documentation** β€” [docs.agentauth.co](https://docs.agentauth.co) - **GitHub** β€” [agentauthco/agentauth](https://github.com/agentauthco/agentauth) - **npm** β€” [@agentauth/core](https://www.npmjs.com/package/@agentauth/core) --- **Built by [AgentAuth](https://agentauth.co)** β€” The Collaboration Layer for AI Agents.