// Copyright 2024-2026 Tymofii Pidlisnyi. Apache-2.0 license. See LICENSE.
// ══════════════════════════════════════════════════════════════════
// DID URI helpers for rail receipts (Phase 4.1 / P12)
// ══════════════════════════════════════════════════════════════════
// Receipts historically carried `signer_did = <raw hex Ed25519 pubkey>`.
// That binds receipt verification to a specific key forever — rotation
// gives no security benefit because the verifier cannot tell whether
// the embedded pubkey is currently retired in the issuer's DID document.
//
// Phase 4.1 / P12 switches signer_did to a DID URI of the form
// `did:<method>:<id>#<keyRef>` (e.g. `did:aps:zABC#key-2`). The verifier
// resolves the DID document via a caller-supplied resolver, walks
// verificationMethod[] to find the matching key, and respects retiredAt:
//
//   - Key not in doc                                → reject
//   - Key retired BEFORE the receipt was signed     → reject (compromise)
//   - Key retired AFTER the receipt was signed      → accept (legitimate
//                                                     post-rotation
//                                                     verification)
//   - Key currently active                          → accept
//
// Receipts continue to carry raw hex pubkeys when issuers don't supply
// agentId/keyRef inputs — that is the legacy compatible-superset path.
// ══════════════════════════════════════════════════════════════════
import { multibaseToHex } from './did.js';
/**
 * Parse a DID URI of the form `<agentId>#<keyRef>` into its parts. The
 * agentId is the full DID (e.g. `did:aps:zABC`), keyRef is the fragment
 * after the first `#` (e.g. `key-2`). Returns null when the input is
 * not a valid DID URI with a fragment.
 */
export function parseDidUri(s) {
    if (typeof s !== 'string' || !s.startsWith('did:'))
        return null;
    const hashIdx = s.indexOf('#');
    if (hashIdx <= 4)
        return null;
    const agentId = s.slice(0, hashIdx);
    const keyRef = s.slice(hashIdx + 1);
    if (agentId.length === 0 || keyRef.length === 0)
        return null;
    // Guard against multiple '#' characters which are not valid in DID URIs.
    if (keyRef.includes('#'))
        return null;
    return { agentId, keyRef };
}
/**
 * Build a DID URI from agentId and keyRef. agentId must already be a
 * `did:<method>:<id>` form; keyRef is the fragment without the '#'.
 */
export function buildDidUri(agentId, keyRef) {
    if (!agentId.startsWith('did:')) {
        throw new Error(`buildDidUri: agentId must be a DID, got '${agentId}'`);
    }
    if (keyRef.length === 0) {
        throw new Error('buildDidUri: keyRef must be non-empty');
    }
    if (keyRef.includes('#')) {
        throw new Error("buildDidUri: keyRef must not contain '#'");
    }
    if (agentId.includes('#')) {
        throw new Error("buildDidUri: agentId must not contain '#'");
    }
    return `${agentId}#${keyRef}`;
}
/**
 * Resolve a DID URI against a RotatableDIDDocument and return the
 * verificationMethod entry plus a `retired` flag that takes signing
 * time into account.
 *
 * Returns:
 *   - `null` if the keyRef does not exist in the document, or if the
 *     URI's agentId does not match the document's id (caller passed
 *     the wrong document).
 *   - `{ method, retired: false }` when the key is currently active OR
 *     was retired after `issuedAtMs` (the receipt was signed while the
 *     key was still active — acceptable to verify post-rotation).
 *   - `{ method, retired: true }` when the key was retired before
 *     `issuedAtMs` (compromise mode — verifier MUST reject the
 *     signature regardless of mathematical validity).
 *
 * @param didDoc      The agent's RotatableDIDDocument.
 * @param didUri      The DID URI carried in the receipt's signer_did.
 * @param nowMs       Wall-clock at verification time. Defaults to Date.now().
 * @param issuedAtMs  Wall-clock when the receipt was signed. Defaults to
 *                    nowMs (which collapses to "key must be currently
 *                    active") when omitted.
 */
export function resolveVerificationMethod(didDoc, didUri, nowMs, issuedAtMs) {
    const parsed = parseDidUri(didUri);
    if (!parsed)
        return null;
    if (didDoc.id !== parsed.agentId)
        return null;
    const method = didDoc.verificationMethod.find((vm) => vm.id === didUri);
    if (!method)
        return null;
    const _now = nowMs ?? Date.now();
    const _issued = issuedAtMs ?? _now;
    if (method.retiredAt) {
        const retiredAtMs = Date.parse(method.retiredAt);
        if (Number.isFinite(retiredAtMs) && retiredAtMs <= _issued) {
            // Retired before (or at exactly) the signing instant — reject.
            // The "<= " is deliberate: a key retired at the same millisecond
            // as the signature is treated as compromised.
            return { method, retired: true };
        }
        // retiredAt > issuedAtMs OR malformed retiredAt: accept the signature
        // as a legitimate pre-rotation issuance verifying post-rotation.
        return { method, retired: false };
    }
    return { method, retired: false };
}
/**
 * Convenience: extract the raw hex Ed25519 pubkey from a resolved
 * verificationMethod entry. Wraps `multibaseToHex(method.publicKeyMultibase)`
 * so callers don't need to import from did.js.
 */
export function publicKeyHexFromMethod(method) {
    return multibaseToHex(method.publicKeyMultibase);
}
//# sourceMappingURL=did-uri.js.map