// Passport signing + verification.
//
// Locked Architectural Invariant 1 (self-sovereign issuance): the platform is
// the passport authority. The org's Ed25519 key signs `signature`. The platform
// CA's Ed25519 key signs `platform_countersignature` (per Shield-R2 #9 — distinguishes
// platform-issued from provisional cryptographically, not by self-attested claim).
//
// Locked Architectural Invariant 4 (offline-verifiable): given a cached org
// public key + the platform CA public key, a relying party verifies passports
// with no platform contact.
//
// Locked Architectural Invariant 5 (structured response): verification returns
// a discriminated union; never throws on malformed input.
import { sign, verify, canonicalJson } from "./ed25519.js";
const SIG_FIELDS = [
    "passport_id",
    "agent_id",
    "issuing_org_id",
    "model_lineage_hash",
    "governance_policy_version",
    "authorized_scope",
    "issued_at",
    "expires_at",
    "pack_binding",
    "public_key_b64", // Invariant 4 — pub key embedded for true offline verify
    "public_key_fingerprint",
    "external_identity_references", // Invariant 9 — bound IdP identities
    "renewal_generation", // T1 — reputation-portable renewals
    "predecessor_passport_id",
];
/** Builds the canonical bytes that get signed (org + platform sign the same payload). */
function payloadForSigning(claims) {
    const subset = {};
    for (const k of SIG_FIELDS)
        subset[k] = claims[k];
    const json = canonicalJson(subset);
    return new TextEncoder().encode(json);
}
/**
 * Sign a passport. The org's private key signs the canonical payload; the
 * platform CA private key countersigns. If `platformPrivateKey` is omitted,
 * the result is a provisional passport (no countersignature).
 */
export function signPassport(args) {
    const payload = payloadForSigning(args.claims);
    const signature = sign(payload, args.orgPrivateKey);
    const platform_countersignature = args.platformPrivateKey
        ? sign(payload, args.platformPrivateKey)
        : null;
    return {
        ...args.claims,
        signature,
        platform_countersignature,
        provisional: platform_countersignature === null,
    };
}
/**
 * Verify a passport. Returns a discriminated union — NEVER throws on bad input
 * (per Locked Invariant 5). The provisional case is reachable only when
 * `requireCountersignature: false` is opted into by the verifying org.
 */
export function verifyPassport(passport, opts) {
    const requireCountersignature = opts.requireCountersignature ?? true;
    const now = opts.now ?? new Date();
    if (!isSignedPassport(passport))
        return { valid: false, reason: "malformed" };
    // Expiry check (Invariant 5: never throw; structured failure instead)
    const exp = Date.parse(passport.expires_at);
    if (Number.isNaN(exp) || exp <= now.getTime()) {
        return { valid: false, reason: "expired" };
    }
    const payload = payloadForSigning(passport);
    if (!verify(passport.signature, payload, opts.orgPublicKey)) {
        return { valid: false, reason: "signature_mismatch" };
    }
    if (requireCountersignature) {
        if (!passport.platform_countersignature) {
            return { valid: false, reason: "missing_platform_countersignature" };
        }
        if (!verify(passport.platform_countersignature, payload, opts.platformPublicKey)) {
            return { valid: false, reason: "platform_countersignature_mismatch" };
        }
    }
    return { valid: true, passport };
}
function isSignedPassport(p) {
    if (typeof p !== "object" || p === null)
        return false;
    const o = p;
    return (typeof o.passport_id === "string" &&
        typeof o.agent_id === "string" &&
        typeof o.issuing_org_id === "string" &&
        typeof o.signature === "string" &&
        o.signature.length === 86 &&
        Array.isArray(o.pack_binding) && o.pack_binding.length >= 1 &&
        typeof o.public_key_b64 === "string" &&
        typeof o.renewal_generation === "number");
}
/**
 * True offline verification per Locked Invariant 4: extract the per-agent
 * public key directly from the passport (it's embedded + signed). Verifier
 * needs ONLY the passport + the platform CA public key — no platform contact,
 * no out-of-band key channel.
 *
 * Substituting a different key into the passport would invalidate the
 * signature since the key bytes are part of the signed payload.
 */
export function verifyPassportOffline(passport, platformPublicKey, options = {}) {
    if (!isSignedPassport(passport))
        return { valid: false, reason: "malformed" };
    let orgPublicKey;
    try {
        orgPublicKey = Buffer.from(passport.public_key_b64, "base64url");
    }
    catch {
        return { valid: false, reason: "malformed" };
    }
    if (orgPublicKey.length !== 32)
        return { valid: false, reason: "malformed" };
    const verifyOpts = {
        orgPublicKey,
        platformPublicKey,
        ...(options.requireCountersignature !== undefined ? { requireCountersignature: options.requireCountersignature } : {}),
        ...(options.now !== undefined ? { now: options.now } : {}),
    };
    return verifyPassport(passport, verifyOpts);
}
//# sourceMappingURL=passport-signer.js.map