@openagentid/oas-crypto
v1.0.2
Published
Cryptographic primitives for the Open Agent Specification — Ed25519, HKDF-SHA256, BLAKE3, JCS, encoding
Readme
@openagentid/oas-crypto
Cryptographic primitives for the Open Agent Specification (OAS).
This package provides Ed25519 key management, HKDF-SHA256 key derivation, BLAKE3 content hashing, JCS canonicalization (RFC 8785), multibase/base64url encoding, and AgentLineageProof2025 generation and verification.
Installation
npm i @openagentid/oas-cryptoRequirements: Node.js >= 20, ESM-only.
Usage
Generate a keypair and sign a message
import { OasKeyPair } from '@openagentid/oas-crypto';
const kp = await OasKeyPair.generate();
const message = new TextEncoder().encode('hello OAS');
const signature = await kp.sign(message);
const valid = await OasKeyPair.verifyWithKey(kp.verifyingKeyBytes, message, signature);
console.log(valid); // trueReconstruct a keypair from bytes
import { OasKeyPair } from '@openagentid/oas-crypto';
const kp1 = await OasKeyPair.generate();
const bytes = kp1.signingKeyBytes;
const kp2 = await OasKeyPair.fromSigningKeyBytes(bytes);
// kp1 and kp2 produce identical signaturesDerive a child keypair
import { OasKeyPair, deriveChildKeypair } from '@openagentid/oas-crypto';
const parent = await OasKeyPair.generate();
const child = await deriveChildKeypair(parent, '/agent-my-bot');
// child has a deterministic keypair derived from parent + pathGenerate and verify a lineage proof
import { OasKeyPair, AgentLineageProof } from '@openagentid/oas-crypto';
const parent = await OasKeyPair.generate();
const proof = await AgentLineageProof.generate(
parent,
'did:oas:l1fe:hmr:alice',
'did:oas:l1fe:agent:my-bot',
'/agent-my-bot',
);
const valid = await AgentLineageProof.verify(proof);
console.log(valid); // true
// Verify against a known public key (safer)
const validWithKey = await AgentLineageProof.verifyWithKey(
proof,
parent.publicKeyMultibase(),
);
console.log(validWithKey); // trueHash content with BLAKE3
import { blake3Hash, blake3HashHex } from '@openagentid/oas-crypto';
const data = new TextEncoder().encode('hello');
const hash = blake3Hash(data); // Uint8Array (32 bytes)
const hex = blake3HashHex(data); // 64-char hex stringCanonicalize JSON with JCS
import { jcsCanonicalizeToString, jcsCanonicalize } from '@openagentid/oas-crypto';
const canonical = jcsCanonicalizeToString({ b: 2, a: 1 });
console.log(canonical); // '{"a":1,"b":2}'
const bytes = jcsCanonicalize({ childDid: 'did:oas:l1fe:agent:bot' });
// UTF-8 bytes of the canonical JSONEncode and decode
import {
multibaseEncode,
multibaseDecode,
base64urlEncode,
base64urlDecode,
} from '@openagentid/oas-crypto';
// Multibase (base58btc with 'z' prefix)
const encoded = multibaseEncode(new Uint8Array([1, 2, 3]));
console.log(encoded.startsWith('z')); // true
const decoded = multibaseDecode(encoded);
// Base64url (RFC 4648, no padding)
const b64 = base64urlEncode(new Uint8Array([72, 101, 108, 108, 111]));
console.log(b64); // 'SGVsbG8'
const original = base64urlDecode(b64);API Reference
OasKeyPair (class)
Ed25519 keypair for an OAS entity. Private keys are never exposed via toString() or toJSON().
class OasKeyPair {
/**
* Generates a new random Ed25519 keypair.
*/
static async generate(): Promise<OasKeyPair>;
/**
* Constructs a keypair from raw 32-byte signing key material.
* The verifying key is automatically derived.
* @throws CryptoError if not 32 bytes.
*/
static async fromSigningKeyBytes(bytes: Uint8Array): Promise<OasKeyPair>;
/**
* Validates and returns a copy of 32-byte verifying key bytes.
* @throws CryptoError if not 32 bytes.
*/
static verifyingKeyFromBytes(bytes: Uint8Array): Uint8Array;
/**
* Verifies an Ed25519 signature against a public key.
* Uses constant-time comparison internally.
* @throws CryptoError if key or signature bytes have wrong length.
*/
static async verifyWithKey(
publicKeyBytes: Uint8Array,
message: Uint8Array,
signatureBytes: Uint8Array,
): Promise<boolean>;
/** Returns a copy of the raw 32-byte signing (private) key bytes. */
get signingKeyBytes(): Uint8Array;
/** Returns a copy of the raw 32-byte verifying (public) key bytes. */
get verifyingKeyBytes(): Uint8Array;
/** Signs a message. Returns a 64-byte Ed25519 signature. */
async sign(message: Uint8Array): Promise<Uint8Array>;
/** Returns the public key as multibase base58btc (with 'z' prefix). */
publicKeyMultibase(): string;
/** Debug-safe string representation. Private key is always redacted. */
toString(): string;
/** JSON-safe representation. Private key is excluded. */
toJSON(): { verifyingKey: string };
}AgentLineageProof
Lineage proof generation and verification (OAS Spec Sections 9.4, 9.5).
const PROOF_TYPE = 'AgentLineageProof2025';
const PROOF_ALGORITHM = 'HKDF-SHA256-Ed25519';
interface AgentLineageProofData {
readonly proofType: typeof PROOF_TYPE;
readonly parentDid: string;
readonly childDid: string;
readonly derivationPath: string;
readonly algorithm: typeof PROOF_ALGORITHM;
readonly publicKeyMultibase: string;
readonly signature: string;
}
const AgentLineageProof = {
/**
* Generates a new lineage proof signed by the parent keypair.
* @throws CryptoError on signing or canonicalization failure.
*/
async generate(
parentKeypair: OasKeyPair,
parentDid: string,
childDid: string,
derivationPath: string,
): Promise<AgentLineageProofData>;
/**
* Verifies a lineage proof using the embedded public key.
* @throws CryptoError on decoding or verification errors.
*/
async verify(proof: AgentLineageProofData): Promise<boolean>;
/**
* Verifies a lineage proof against an externally provided public key.
* @throws CryptoError on decoding or verification errors.
*/
async verifyWithKey(
proof: AgentLineageProofData,
publicKeyMultibase: string,
): Promise<boolean>;
/** Serializes a proof to a plain JSON object. */
toJSON(proof: AgentLineageProofData): Record<string, string>;
/**
* Deserializes a proof from a plain JSON object.
* @throws CryptoError if required fields are missing.
*/
fromJSON(obj: Record<string, unknown>): AgentLineageProofData;
};Key derivation
/**
* Derives a child Ed25519 keypair from a parent keypair using HKDF-SHA256.
* IKM: parent signing key. Salt: parent verifying key. Info: derivation path.
* @throws CryptoError on derivation failure.
*/
async function deriveChildKeypair(
parent: OasKeyPair,
derivationPath: string,
): Promise<OasKeyPair>;
/**
* Low-level HKDF-SHA256 key material derivation. Returns 32 bytes.
* @throws CryptoError on derivation failure.
*/
function deriveKeyMaterial(
ikm: Uint8Array,
salt: Uint8Array,
info: Uint8Array,
): Uint8Array;Hashing
/** Computes a 32-byte BLAKE3 hash. */
function blake3Hash(data: Uint8Array): Uint8Array;
/** Computes a BLAKE3 hash as a 64-character lowercase hex string. */
function blake3HashHex(data: Uint8Array): string;Canonicalization
/**
* Canonicalizes a JSON value to UTF-8 bytes per RFC 8785 (JCS).
* @throws CryptoError on unsupported values (undefined, functions, circular refs).
*/
function jcsCanonicalize(value: unknown): Uint8Array;
/**
* Canonicalizes a JSON value to a string per RFC 8785 (JCS).
* @throws CryptoError on unsupported values.
*/
function jcsCanonicalizeToString(value: unknown): string;Encoding
/** Encodes bytes as multibase base58btc (with 'z' prefix). */
function multibaseEncode(bytes: Uint8Array): string;
/**
* Decodes a multibase base58btc string (strips 'z' prefix).
* @throws CryptoError if prefix is not 'z' or decoding fails.
*/
function multibaseDecode(encoded: string): Uint8Array;
/** Encodes bytes as base64url (RFC 4648, no padding). */
function base64urlEncode(bytes: Uint8Array): string;
/**
* Decodes a base64url string to bytes.
* @throws CryptoError on decoding failure.
*/
function base64urlDecode(encoded: string): Uint8Array;Error classes
class CryptoError extends Error {
readonly code: CryptoErrorCode;
readonly details: Readonly<Record<string, unknown>>;
constructor(code: CryptoErrorCode, message: string, details?: Record<string, unknown>);
}
const CryptoErrorCode = {
InvalidSignature: 'INVALID_SIGNATURE',
InvalidKeyLength: 'INVALID_KEY_LENGTH',
DerivationFailed: 'DERIVATION_FAILED',
MultibaseDecodeFailed: 'MULTIBASE_DECODE_FAILED',
Base64DecodeFailed: 'BASE64_DECODE_FAILED',
CanonicalizationFailed: 'CANONICALIZATION_FAILED',
InvalidProofPayload: 'INVALID_PROOF_PAYLOAD',
KeyConstructionFailed: 'KEY_CONSTRUCTION_FAILED',
} as const;
type CryptoErrorCode = (typeof CryptoErrorCode)[keyof typeof CryptoErrorCode];Dependencies
| Package | Purpose |
|---------|---------|
| @noble/ed25519 2.1.0 | Ed25519 signatures (audited by Cure53) |
| @noble/hashes 1.6.1 | SHA-256, HKDF, BLAKE3 (audited by Cure53) |
| @scure/base 1.2.1 | Base58, Base64url encoding |
All dependencies are audited, pure-JavaScript, and have zero native addons.
Security
- Private key bytes are stored as
Uint8Array, never as strings toString()andtoJSON()always redact the signing key- Constant-time signature verification via
@noble/ed25519 - Callers should zero signing key bytes when done:
keypair.signingKeyBytes.fill(0)
License
Copyright © 2026 L1fe Labs, Inc.
Licensed under either of Apache License 2.0 or MIT license, at your option.
