/* pq_crypto.c — Algorithm registry for the post-quantum crypto expansion.
 *
 * Phase 1: introduces the crypto_alg_t abstraction and size constants for all
 * six algorithms (secp256k1, ed25519, x25519, ML-DSA-65, SLH-DSA-128s,
 * ML-KEM-768). No new crypto is implemented in this phase — secp256k1 continues
 * to work through the new abstraction, and the other algorithms return
 * CRYPTO_ALG_UNKNOWN / NULL from the registry helpers until later phases wire
 * up real keygen/sign/decap.
 */

/* NSIGNER_HEADERLESS_DECLS_BEGIN */
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
#include <cJSON.h>

/* from secure_mem.h */


/*
 * Secure memory buffer — mlock'd, zeroized on free.
 * Used for mnemonic phrases, private keys, and any sensitive material.
 */
typedef struct {
    void *data;       /* pointer to locked allocation */
    size_t size;      /* usable size in bytes */
    int locked;       /* 1 if mlock succeeded */
} secure_buf_t;

/* Allocate a secure buffer of `size` bytes. Returns 0 on success, -1 on failure. */
int secure_buf_alloc(secure_buf_t *buf, size_t size);

/* Zeroize and free a secure buffer. Always succeeds (idempotent). */
void secure_buf_free(secure_buf_t *buf);

/* Zeroize `len` bytes at `ptr` in a way the compiler cannot optimize away. */
void secure_memzero(void *ptr, size_t len);


/* from mnemonic.h */


/* Maximum mnemonic length: 24 words * 10 chars avg + spaces + null = 256 is safe */
#define MNEMONIC_MAX_LEN 256

/*
 * Mnemonic state — holds the loaded mnemonic in secure memory.
 * Only one mnemonic is active at a time per process.
 */
typedef struct {
    secure_buf_t buf;       /* secure storage for the mnemonic string */
    int loaded;             /* 1 if a mnemonic is currently loaded */
    int word_count;         /* 12, 15, 18, 21, or 24 */
} mnemonic_state_t;

/* Initialize mnemonic state (must be called before use). */
void mnemonic_init(mnemonic_state_t *state);

/* Load a mnemonic string into secure memory. Validates word count (12/15/18/21/24).
 * Returns 0 on success, -1 on invalid input, -2 on memory error. */
int mnemonic_load(mnemonic_state_t *state, const char *phrase);

/* Zeroize and unload the mnemonic. Idempotent. */
void mnemonic_unload(mnemonic_state_t *state);

/* Check if a mnemonic is currently loaded. */
int mnemonic_is_loaded(const mnemonic_state_t *state);

/* Get the mnemonic string (only valid while loaded). Returns NULL if not loaded. */
const char *mnemonic_get_phrase(const mnemonic_state_t *state);

/* Generate a new BIP-39 mnemonic phrase (12/15/18/21/24 words) into out.
 * Returns 0 on success, -1 on invalid arguments or generation failure. */
int mnemonic_generate(int word_count, char *out, size_t out_len);


/* from role_table.h */


/* Maximum limits */
#define ROLE_NAME_MAX 64
#define ROLE_PATH_MAX 128
#define ROLE_PURPOSE_MAX 32
#define ROLE_CURVE_MAX 16
#define ROLE_PUBKEY_HEX_MAX 66 /* 64 hex chars + null + pad */
#define ROLE_TABLE_MAX_ENTRIES 256

/* Purpose enum for fast comparison (string form kept for config/display) */
typedef enum {
    PURPOSE_NOSTR = 0,
    PURPOSE_BITCOIN,
    PURPOSE_SSH,
    PURPOSE_AGE,
    PURPOSE_FIPS,
    PURPOSE_PQ_SIG,      /* post-quantum signatures (ML-DSA, SLH-DSA) */
    PURPOSE_PQ_KEM,      /* post-quantum key encapsulation (ML-KEM) */
    PURPOSE_UNKNOWN
} role_purpose_t;

/* Curve enum */
typedef enum {
    CURVE_SECP256K1 = 0,
    CURVE_ED25519,
    CURVE_X25519,
    CURVE_ML_DSA_65,
    CURVE_SLH_DSA_128S,
    CURVE_ML_KEM_768,
    CURVE_UNKNOWN
} role_curve_t;

/* Selector type — how this role's key is addressed */
typedef enum {
    SELECTOR_NOSTR_INDEX, /* uses nostr_index shorthand */
    SELECTOR_ROLE_PATH    /* uses explicit full path */
} role_selector_type_t;

/* A single role entry */
typedef struct {
    char name[ROLE_NAME_MAX];
    char purpose_str[ROLE_PURPOSE_MAX];
    char curve_str[ROLE_CURVE_MAX];
    role_purpose_t purpose;
    role_curve_t curve;
    role_selector_type_t selector_type;
    int nostr_index;                         /* valid if selector_type == SELECTOR_NOSTR_INDEX */
    char role_path[ROLE_PATH_MAX];           /* valid if selector_type == SELECTOR_ROLE_PATH */
    char pubkey_hex[ROLE_PUBKEY_HEX_MAX];    /* filled after derivation, empty until then */
    int derived;                             /* 1 if pubkey_hex has been populated */
} role_entry_t;

/* The role table */
typedef struct {
    role_entry_t entries[ROLE_TABLE_MAX_ENTRIES];
    int count;
} role_table_t;

/* Initialize an empty role table */
void role_table_init(role_table_t *table);

/* Add a role entry. Returns 0 on success, -1 if table full, -2 if name duplicate. */
int role_table_add(role_table_t *table, const role_entry_t *entry);

/* Find a role by name. Returns pointer to entry or NULL. */
role_entry_t *role_table_find_by_name(role_table_t *table, const char *name);

/* Find a role by nostr_index. Returns pointer or NULL. */
role_entry_t *role_table_find_by_nostr_index(role_table_t *table, int index);

/* Find a role by role_path. Returns pointer or NULL. */
role_entry_t *role_table_find_by_path(role_table_t *table, const char *path);

/* Get the default role (named "main"). Returns pointer or NULL if no "main" role. */
role_entry_t *role_table_get_default(role_table_t *table);

/* Parse purpose string to enum */
role_purpose_t role_purpose_from_str(const char *s);

/* Parse curve string to enum */
role_curve_t role_curve_from_str(const char *s);

/* Purpose enum to string */
const char *role_purpose_to_str(role_purpose_t p);

/* Curve enum to string */
const char *role_curve_to_str(role_curve_t c);

/* Register a nostr-index role if missing. Returns 0 on success, -1 on error. */
int role_table_register_nostr_index(role_table_t *table, int nostr_index);


/* from selector.h */


/* Error codes for selector resolution */
#define SELECTOR_OK                 0
#define SELECTOR_ERR_AMBIGUOUS     -1   /* multiple selectors specified */
#define SELECTOR_ERR_NOT_FOUND     -2   /* no matching role in table */
#define SELECTOR_ERR_NO_DEFAULT    -3   /* no selector given and no "main" role exists */

/* Parsed selector from a request's options object */
typedef struct {
    int has_role;          /* 1 if "role" field was present */
    char role_name[ROLE_NAME_MAX];

    int has_nostr_index;   /* 1 if "nostr_index" field was present */
    int nostr_index;

    int has_role_path;     /* 1 if "role_path" field was present */
    char role_path[ROLE_PATH_MAX];
} selector_request_t;

/* Initialize a selector request (all fields zeroed/unset) */
void selector_request_init(selector_request_t *req);

/*
 * Resolve a selector request against the role table.
 * On success (returns SELECTOR_OK), *out points to the matched role_entry_t.
 * On failure, returns one of the SELECTOR_ERR_* codes and *out is NULL.
 */
int selector_resolve(const selector_request_t *req, role_table_t *table, role_entry_t **out);

/*
 * Return a human-readable error string for a selector error code.
 */
const char *selector_strerror(int err);


/* from enforcement.h */


/* Error codes */
#define ENFORCE_OK                0
#define ENFORCE_ERR_PURPOSE      -1 /* purpose mismatch */
#define ENFORCE_ERR_CURVE        -2 /* curve mismatch */
#define ENFORCE_ERR_UNKNOWN_VERB -3 /* verb not recognized */
#define ENFORCE_ERR_ALGORITHM  -4 /* algorithm not valid for verb */

/* New algorithm-based verb defines (algorithm is a parameter, not implicit) */
#define VERB_SIGN              "sign"
#define VERB_VERIFY            "verify"
#define VERB_ENCAPSULATE       "encapsulate"
#define VERB_DECAPSULATE       "decapsulate"
#define VERB_DERIVE_SHARED     "derive_shared_secret"

#define ENFORCE_ERR_ALGORITHM  -4 /* algorithm not valid for verb */

/* New algorithm-based verb defines (algorithm is a parameter, not implicit) */
#define VERB_SIGN              "sign"
#define VERB_VERIFY            "verify"
#define VERB_ENCAPSULATE       "encapsulate"
#define VERB_DECAPSULATE       "decapsulate"
#define VERB_DERIVE_SHARED     "derive_shared_secret"


/* Known verbs */
#define VERB_SIGN_EVENT     "sign_event"
#define VERB_GET_PUBLIC_KEY "get_public_key"
#define VERB_NIP44_ENCRYPT  "nip44_encrypt"
#define VERB_NIP44_DECRYPT  "nip44_decrypt"
#define VERB_NIP04_ENCRYPT  "nip04_encrypt"
#define VERB_NIP04_DECRYPT  "nip04_decrypt"

/*
 * Check whether `verb` is allowed to execute against `role`.
 * Returns ENFORCE_OK if allowed, or an ENFORCE_ERR_* code.
 *
 * The enforcement rules are:
 *   - All nostr verbs (sign_event, get_public_key, nip44_*, nip04_*) require:
 *     purpose == PURPOSE_NOSTR and curve == CURVE_SECP256K1
 *   - Unknown verbs return ENFORCE_ERR_UNKNOWN_VERB (fail-closed).
 */
int enforce_verb_role(const char *verb, const role_entry_t *role);

/*
 * Return a human-readable error string for an enforcement error code.
 */
const char *enforce_strerror(int err);


/* from policy.h */


#define POLICY_MAX_ENTRIES    32
#define POLICY_MAX_VERBS      16
#define POLICY_MAX_ROLES      16
#define POLICY_MAX_PURPOSES   8
#define POLICY_VERB_MAX_LEN   32
#define POLICY_CALLER_MAX_LEN 160
#define POLICY_MAX_ALGS       16
#define POLICY_MAX_ALGS       16

/* Prompt behavior */
typedef enum {
    PROMPT_NEVER = 0,
    PROMPT_FIRST_PER_BOOT,
    PROMPT_EVERY_REQUEST,
    PROMPT_DENY
} prompt_mode_t;

typedef enum {
    POLICY_SOURCE_DEFAULT = 0,   /* the catch-all entry */
    POLICY_SOURCE_PREAPPROVE,    /* from --preapprove CLI flag */
    POLICY_SOURCE_SESSION_GRANT  /* from prompt [a] during session */
} policy_source_t;

/* A single policy entry */
typedef struct {
    char caller[POLICY_CALLER_MAX_LEN];   /* e.g. "uid:1000" or "*" for any */
    char verbs[POLICY_MAX_VERBS][POLICY_VERB_MAX_LEN];
    int verb_count;
    char roles[POLICY_MAX_ROLES][ROLE_NAME_MAX];
    int role_count;
    char purposes[POLICY_MAX_PURPOSES][ROLE_PURPOSE_MAX];
    int purpose_count;
    /* Algorithm-based (new) */
    char algorithms[16][32];  /* algorithm names; POLICY_MAX_ALGS */
    int alg_count;
    int index_min;            /* -1 = any */
    int index_max;            /* -1 = any */
    /* Common */
    prompt_mode_t prompt;
    policy_source_t source;
} policy_entry_t;

/* Policy table */
typedef struct {
    policy_entry_t entries[POLICY_MAX_ENTRIES];
    int count;
} policy_table_t;

/* Policy check result */
#define POLICY_ALLOW     0
#define POLICY_DENY     -1
#define POLICY_PROMPT   -2   /* would need user confirmation */
#define POLICY_NO_MATCH -3   /* no policy entry matched (fail-closed = deny) */

/* Initialize policy table */
void policy_table_init(policy_table_t *table);

/* Initialize default policy: allow same-uid, deny others */
void policy_init_default(policy_table_t *table, uid_t owner_uid);

/* Add a policy entry. Returns 0 on success, -1 if full. */
int policy_table_add(policy_table_t *table, const policy_entry_t *entry);

/*
 * Check whether caller_id is allowed to invoke `verb` on `role_name` with given `purpose`.
 * Returns POLICY_ALLOW, POLICY_DENY, POLICY_PROMPT, or POLICY_NO_MATCH.
 */
int policy_check(const policy_table_t *table, const char *caller_id,
                 const char *verb, const char *role_name, const char *purpose,
                 policy_source_t *out_source);

/* Check whether caller_id is allowed to invoke `verb` with the given
 * algorithm and index (algorithm-based policy). Returns POLICY_ALLOW,
 * POLICY_DENY, POLICY_PROMPT, or POLICY_NO_MATCH. */
int policy_check_algorithm(const policy_table_t *table, const char *caller_id,
                           const char *verb, const char *algorithm, int index,
                           policy_source_t *out_source);

/* Check whether caller_id is allowed to invoke `verb` with the given
 * algorithm and index (algorithm-based policy). Returns POLICY_ALLOW,
 * POLICY_DENY, POLICY_PROMPT, or POLICY_NO_MATCH. */
int policy_check_algorithm(const policy_table_t *table, const char *caller_id,
                           const char *verb, const char *algorithm, int index,
                           policy_source_t *out_source);

/* Parse prompt mode from string */
prompt_mode_t prompt_mode_from_str(const char *s);

/* Prompt mode to string */
const char *prompt_mode_to_str(prompt_mode_t m);


/* from pq_crypto.h */


/* Algorithm identifiers */
typedef enum {
    CRYPTO_ALG_SECP256K1 = 0,   /* existing, Nostr */
    CRYPTO_ALG_ED25519,         /* new, SSH signatures */
    CRYPTO_ALG_X25519,          /* new, key agreement */
    CRYPTO_ALG_ML_DSA_65,       /* new, PQ signatures */
    CRYPTO_ALG_SLH_DSA_128S,    /* new, PQ signatures */
    CRYPTO_ALG_ML_KEM_768,      /* new, PQ KEM */
    CRYPTO_ALG_UNKNOWN
} crypto_alg_t;

/* Key sizes for each algorithm (compile-time constants) */
typedef struct {
    size_t priv_key_len;
    size_t pub_key_len;
    size_t sig_len;           /* 0 for KEM */
    size_t ciphertext_len;    /* 0 for signatures */
    size_t shared_secret_len; /* 0 for signatures */
} crypto_alg_sizes_t;

/* Get size info for an algorithm. Returns NULL for CRYPTO_ALG_UNKNOWN. */
const crypto_alg_sizes_t *crypto_alg_get_sizes(crypto_alg_t alg);

/* Map role_curve_t + role_purpose_t to crypto_alg_t.
 * Returns CRYPTO_ALG_UNKNOWN for unsupported combinations. */
crypto_alg_t crypto_alg_from_role(role_curve_t curve, role_purpose_t purpose);

/* Convert crypto_alg_t to string. Returns NULL for unknown. */
const char *crypto_alg_to_str(crypto_alg_t alg);

/* Parse string to crypto_alg_t. Returns CRYPTO_ALG_UNKNOWN for unrecognized. */
crypto_alg_t crypto_alg_from_str(const char *s);

/* ed25519: derive keypair from a 32-byte seed.
 * Uses OpenSSL EVP_PKEY. The seed is used as the raw private key.
 * priv_out and pub_out must be buffers of at least 32 bytes each.
 * Returns 0 on success, -1 on error. */
int crypto_ed25519_keygen_from_seed(const unsigned char *seed, size_t seed_len,
                                     unsigned char *priv_out, unsigned char *pub_out);

/* ed25519: sign a message. priv is 32-byte private key.
 * sig_out must be at least 64 bytes.
 * Returns 0 on success, -1 on error. */
int crypto_ed25519_sign(const unsigned char *priv, size_t priv_len,
                         const unsigned char *msg, size_t msg_len,
                         unsigned char *sig_out, size_t *sig_out_len);

/* ed25519: verify a signature. pub is 32-byte public key.
 * Returns 0 on valid, 1 on invalid, -1 on error. */
int crypto_ed25519_verify(const unsigned char *pub, size_t pub_len,
                           const unsigned char *msg, size_t msg_len,
                           const unsigned char *sig, size_t sig_len);

/* x25519: derive keypair from a 32-byte seed.
 * priv_out and pub_out must be at least 32 bytes each.
 * Returns 0 on success, -1 on error. */
int crypto_x25519_keygen_from_seed(const unsigned char *seed, size_t seed_len,
                                    unsigned char *priv_out, unsigned char *pub_out);

/* x25519: derive shared secret from our private key and peer's public key.
 * shared_out must be at least 32 bytes.
 * Returns 0 on success, -1 on error. */
int crypto_x25519_ecdh(const unsigned char *our_priv, size_t priv_len,
                        const unsigned char *peer_pub, size_t pub_len,
                        unsigned char *shared_out, size_t *shared_out_len);

/* Derive a 32-byte seed from a mnemonic using a BIP-44 path.
 * path is a string like "m/44'/102001'/0'/0'/0'".
 * Uses BIP-32/SLIP-0010 HMAC-SHA512 derivation (all-hardened).
 * seed_out must be at least 32 bytes.
 * Returns 0 on success, -1 on error. */
int crypto_derive_seed_from_mnemonic(const char *mnemonic, const char *path,
                                      unsigned char *seed_out, size_t seed_out_len);

/* ML-DSA-65: generate keypair from a 32-byte seed (deterministic).
 * priv_out must be at least 4032 bytes, pub_out at least 1952 bytes.
 * Returns 0 on success, -1 on error. */
int crypto_ml_dsa_65_keygen_from_seed(const unsigned char *seed, size_t seed_len,
                                       unsigned char *priv_out, unsigned char *pub_out);

/* ML-DSA-65: sign a message. priv is 4032-byte private key.
 * sig_out must be at least 3309 bytes. Returns 0 on success, -1 on error. */
int crypto_ml_dsa_65_sign(const unsigned char *priv, size_t priv_len,
                           const unsigned char *msg, size_t msg_len,
                           unsigned char *sig_out, size_t *sig_out_len);

/* ML-DSA-65: verify a signature. pub is 1952-byte public key.
 * Returns 0 on valid, 1 on invalid, -1 on error. */
int crypto_ml_dsa_65_verify(const unsigned char *pub, size_t pub_len,
                              const unsigned char *msg, size_t msg_len,
                              const unsigned char *sig, size_t sig_len);

/* SLH-DSA-128s: generate keypair from a 32-byte seed (deterministic).
 * priv_out must be at least 64 bytes, pub_out at least 32 bytes.
 * Returns 0 on success, -1 on error. */
int crypto_slh_dsa_128s_keygen_from_seed(const unsigned char *seed, size_t seed_len,
                                           unsigned char *priv_out, unsigned char *pub_out);

/* SLH-DSA-128s: sign a message. priv is 64-byte private key.
 * sig_out must be at least 7856 bytes. Returns 0 on success, -1 on error. */
int crypto_slh_dsa_128s_sign(const unsigned char *priv, size_t priv_len,
                               const unsigned char *msg, size_t msg_len,
                               unsigned char *sig_out, size_t *sig_out_len);

/* SLH-DSA-128s: verify a signature. pub is 32-byte public key.
 * Returns 0 on valid, 1 on invalid, -1 on error. */
int crypto_slh_dsa_128s_verify(const unsigned char *pub, size_t pub_len,
                                  const unsigned char *msg, size_t msg_len,
                                  const unsigned char *sig, size_t sig_len);

/* ML-KEM-768: generate keypair from a 32-byte seed (deterministic).
 * priv_out must be at least 2400 bytes, pub_out at least 1184 bytes.
 * Seeds the DRBG with the seed, then calls ML-KEM-768 keygen.
 * Returns 0 on success, -1 on error. */
int crypto_ml_kem_768_keygen_from_seed(const unsigned char *seed, size_t seed_len,
                                         unsigned char *priv_out, unsigned char *pub_out);

/* ML-KEM-768: encapsulate. Given a public key, produce ciphertext + shared secret.
 * pub is 1184-byte public key.
 * ct_out must be at least 1088 bytes, ss_out at least 32 bytes.
 * Uses real randomness (OpenSSL RAND_bytes) for the encapsulation randomness.
 * Returns 0 on success, -1 on error. */
int crypto_ml_kem_768_encaps(const unsigned char *pub, size_t pub_len,
                               unsigned char *ct_out, unsigned char *ss_out);

/* ML-KEM-768: decapsulate. Given a secret key and ciphertext, recover shared secret.
 * priv is 2400-byte secret key, ct is 1088-byte ciphertext.
 * ss_out must be at least 32 bytes.
 * Returns 0 on success, -1 on error. */
int crypto_ml_kem_768_decaps(const unsigned char *priv, size_t priv_len,
                               const unsigned char *ct, size_t ct_len,
                               unsigned char *ss_out);

/* Deterministic PRNG for PQ keygen (replaces PQClean randombytes()). */
void pq_drbg_init(const unsigned char *seed, size_t seed_len);
int  pq_drbg_randombytes(unsigned char *buf, size_t len);
void pq_drbg_zeroize(void);


/* from crypto.h */


/* Per-role derived key material (stored in secure memory) */
typedef struct {
    secure_buf_t private_key;   /* mlock'd, variable size per algorithm */
    secure_buf_t public_key;    /* mlock'd, variable size per algorithm */
    char pubkey_hex[8192];      /* hex-encoded public key (PQ pubkeys are large) */
    char npub[128];             /* bech32 npub (secp256k1 only, empty for others) */
    crypto_alg_t alg;           /* which algorithm this key was derived for */
    int valid;
} derived_key_t;

/* Key store — holds derived keys for all roles */
typedef struct {
    derived_key_t keys[ROLE_TABLE_MAX_ENTRIES];
    int count;
} key_store_t;

/* Derive keys for all roles in the table using the loaded mnemonic.
 * Populates key_store and sets role->pubkey_hex and role->derived for each role.
 * Only derives for roles with purpose=nostr and curve=secp256k1 (for now).
 * Returns number of keys derived, or -1 on error. */
int crypto_derive_all(key_store_t *store, role_table_t *table, const mnemonic_state_t *mnemonic);

/* Derive key for exactly one role index. Returns 0 on success, -1 on error. */
int crypto_derive_one(key_store_t *store, role_table_t *table, const mnemonic_state_t *mnemonic, int role_index);

/* Get the derived private key for a role (by table index). Returns NULL if not derived. */
const unsigned char *crypto_get_private_key(const key_store_t *store, int role_index);

/* Get the derived public key hex for a role. Returns NULL if not derived. */
const char *crypto_get_pubkey_hex(const key_store_t *store, int role_index);

/* Sign a Nostr event. event_json is the unsigned event JSON string.
 * Returns a newly-allocated string containing the signed event JSON, or NULL on error.
 * Caller must free() the returned string. */
char *crypto_sign_event(const key_store_t *store, int role_index, const char *event_json);

/* Mine a Nostr event with NIP-13 proof-of-work, then sign it.
 * event_json is the unsigned event JSON string.
 * difficulty: target leading zero bits (0 = no target, mine for full timeout)
 * threads: number of mining threads (1..32)
 * timeout_sec: time budget in seconds (0 = use 600s safety default)
 * Returns a newly-allocated JSON string containing a result object:
 *   {"event":"<signed event json>","achieved_difficulty":N,"target_difficulty":N,
 *    "target_reached":bool,"elapsed_sec":N,"attempts":N}
 * Returns NULL on error. Caller must free() the returned string. */
char *crypto_mine_event(const key_store_t *store, int role_index,
                        const char *event_json, int difficulty,
                        int threads, int timeout_sec);

/* Zeroize all derived keys in the store. */
void crypto_wipe(key_store_t *store);



/* from alg_api.h */


/* Check whether a verb is valid for an algorithm (algorithm-based enforcement).
 * Returns ENFORCE_OK, ENFORCE_ERR_ALGORITHM, or ENFORCE_ERR_UNKNOWN_VERB.
 * Does NOT check purpose — purpose is irrelevant for the new verbs. */
int enforce_verb_algorithm(const char *verb, crypto_alg_t alg);

/* secp256k1 Schnorr (BIP-340) sign arbitrary bytes.
 * priv is 32-byte scalar, pub is 32-byte x-only pubkey, sig_out is 64 bytes.
 * Hashes the message with SHA-256 before signing (like Nostr event signing).
 * Returns 0 on success, -1 on error. */
int crypto_secp256k1_schnorr_sign(const unsigned char *priv, size_t priv_len,
                                    const unsigned char *msg, size_t msg_len,
                                    unsigned char *sig_out, size_t *sig_out_len);

/* secp256k1 Schnorr (BIP-340) verify.
 * pub is 32-byte x-only pubkey, sig is 64 bytes.
 * Returns 0 on valid, 1 on invalid, -1 on error. */
int crypto_secp256k1_schnorr_verify(const unsigned char *pub, size_t pub_len,
                                      const unsigned char *msg, size_t msg_len,
                                      const unsigned char *sig, size_t sig_len);

/* secp256k1 ECDSA sign arbitrary bytes.
 * priv is 32-byte scalar, sig_out must be at least 64 bytes (compact DER r||s).
 * Hashes the message with SHA-256 before signing.
 * Returns 0 on success, -1 on error. */
int crypto_secp256k1_ecdsa_sign(const unsigned char *priv, size_t priv_len,
                                  const unsigned char *msg, size_t msg_len,
                                  unsigned char *sig_out, size_t *sig_out_len);

/* secp256k1 ECDSA verify.
 * pub is 32-byte x-only pubkey (converted internally to compressed form).
 * sig is 64-byte compact (r||s). Returns 0 on valid, 1 on invalid, -1 on error. */
int crypto_secp256k1_ecdsa_verify(const unsigned char *pub, size_t pub_len,
                                    const unsigned char *msg, size_t msg_len,
                                    const unsigned char *sig, size_t sig_len);

/* ---- Algorithm key cache ----
 * On-demand key derivation by algorithm+index, separate from the role-based
 * key_store. Holds up to ALG_KEY_CACHE_MAX derived keys in secure memory.
 * When full, the oldest entry is evicted (FIFO). */

#define ALG_KEY_CACHE_MAX 32

typedef struct {
    crypto_alg_t alg;
    int index;
    secure_buf_t private_key;
    secure_buf_t public_key;
    char pubkey_hex[8192];
    char key_id[17];
    int valid;
} alg_key_entry_t;

typedef struct {
    alg_key_entry_t entries[ALG_KEY_CACHE_MAX];
    int count;
} algorithm_key_cache_t;

/* Initialize an empty cache. */
void alg_key_cache_init(algorithm_key_cache_t *cache);

/* Zeroize and free all entries. Idempotent. */
void alg_key_cache_wipe(algorithm_key_cache_t *cache);

/* Look up a cached entry by (alg, index). Returns NULL if not present. */
const alg_key_entry_t *alg_key_cache_get(algorithm_key_cache_t *cache, crypto_alg_t alg, int index);

/* Derive a key on-demand by (alg, index) and store it in the cache.
 * Uses the standard derivation path for the algorithm.
 * Returns 0 on success, -1 on error. */
int alg_key_cache_derive(algorithm_key_cache_t *cache, const mnemonic_state_t *mnemonic, crypto_alg_t alg, int index);


/* from dispatcher.h */


/* Dispatcher context — holds references to shared state */
typedef struct {
    role_table_t *role_table;
    mnemonic_state_t *mnemonic;
    key_store_t *key_store;
    algorithm_key_cache_t *alg_key_cache;  /* algorithm-based on-demand keys */
} dispatcher_ctx_t;

/* Initialize dispatcher context */
void dispatcher_init(dispatcher_ctx_t *ctx, role_table_t *table, mnemonic_state_t *mnemonic, key_store_t *key_store, algorithm_key_cache_t *alg_key_cache);

/*
 * Process a JSON-RPC request string and produce a JSON-RPC response string.
 *
 * The caller owns the returned string and must free() it.
 * Returns NULL only on catastrophic allocation failure.
 *
 * Response format on success:
 *   { "id": "...", "result": "..." }
 *
 * Response format on error:
 *   { "id": "...", "error": { "code": <int>, "message": "..." } }
 *
 * Error codes:
 *   -32700  Parse error (invalid JSON)
 *   -32600  Invalid request (missing id/method/params)
 *   -32601  Method not found (unknown verb after enforcement)
 *   -32602  Invalid params
 *   1001    ambiguous_role_selector
 *   1002    role_not_found
 *   1003    no_default_role
 *   1004    purpose_mismatch
 *   1005    curve_mismatch
 *   1006    mnemonic_not_loaded
 */
char *dispatcher_handle_request(dispatcher_ctx_t *ctx, const char *json_request);


/* from server.h */


#define SERVER_SOCKET_NAME_MAX 108
#define SERVER_MAX_MSG_SIZE    16777216

/* Caller identity */
typedef struct {
    uid_t uid;
    gid_t gid;
    pid_t pid;
    char caller_id[POLICY_CALLER_MAX_LEN];  /* "uid:<n>" */
} caller_identity_t;

/* Server context */
typedef struct {
    char socket_name[SERVER_SOCKET_NAME_MAX]; /* abstract namespace name (without \0 prefix) */
    char last_error[256];
    int listen_fd;
    int running;
    dispatcher_ctx_t *dispatcher;
    policy_table_t *policy;
    int socket_name_explicit;
} server_ctx_t;

/* Initialize server context. socket_name is the abstract namespace name (e.g. "nsigner").
 * socket_name_explicit should be non-zero when provided via --socket-name override. */
void server_init(server_ctx_t *ctx, const char *socket_name, int socket_name_explicit,
                 dispatcher_ctx_t *dispatcher, policy_table_t *policy);

/* Start listening. Returns 0 on success, -1 on error. */
int server_start(server_ctx_t *ctx);

/* Get human-readable description of last server error. */
const char *server_last_error(const server_ctx_t *ctx);

/* Handle one pending connection (non-blocking). Returns 1 if handled, 0 if nothing pending, -1 on error.
 * activity_cb is called with a description string for the TUI activity log. */
typedef void (*server_activity_cb)(const char *message, void *user_data);
int server_handle_one(server_ctx_t *ctx, server_activity_cb cb, void *cb_data);

/* Stop server and close socket */
void server_stop(server_ctx_t *ctx);

/* Extract caller identity from connected fd */
int server_get_caller(int fd, caller_identity_t *out);


/* from socket_name.h */


/*
 * Generate random socket name in format: nsigner_<word1>_<word2>
 * Returns 0 on success, -1 on error.
 */
int socket_name_random(char *out, size_t out_len);


/* from main.h */
/*
 * nsigner main header - version information
 *
 * Version macros are auto-updated by increment_and_push.sh.
 */


/* Version information (auto-updated by build/version tooling) */
#define NSIGNER_VERSION_MAJOR 0
#define NSIGNER_VERSION_MINOR 0
#define NSIGNER_VERSION_PATCH 2
#define NSIGNER_VERSION "v0.0.2"


/* NSIGNER_HEADERLESS_DECLS_END */

#include <string.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/sha.h>
#include <openssl/rand.h>
#include <nostr_core/utils.h>
#include <nostr_core/nip006.h>
#include <secp256k1.h>
#include <secp256k1_extrakeys.h>
#include <secp256k1_schnorrsig.h>

/* Phase 3: ML-DSA-65 (FIPS 204) and deterministic DRBG */
#include "api.h"

/* Phase 4: SLH-DSA-128s (FIPS 205) — hash-based signatures.
 * Included via the pqclean root path to avoid conflicting with
 * ML-DSA-65's api.h (both are named api.h). */
#include "crypto_sign/slh-dsa-128s/api.h"

/* Phase 5: ML-KEM-768 (FIPS 203) — lattice-based key encapsulation.
 * Included via the pqclean root path to avoid conflicting with
 * the other api.h files. */
#include "crypto_kem/ml-kem-768/api.h"

/* Compile-time size constants for each algorithm.
 * Sources:
 *   secp256k1:   Nostr/Schnorr — 32-byte scalar, 32-byte x-only pubkey, 64-byte sig
 *   ed25519:     RFC 8032 — 32-byte seed, 32-byte pubkey, 64-byte sig
 *   x25519:      RFC 7748 — 32-byte scalar, 32-byte pubkey, 32-byte shared secret
 *   ML-DSA-65:   FIPS 204 — priv 4032, pub 1952, sig 3309
 *   SLH-DSA-128s: FIPS 205 — priv 64, pub 32, sig 7856
 *   ML-KEM-768:  FIPS 203 — priv 2400, pub 1184, ciphertext 1088, shared secret 32
 */
static const crypto_alg_sizes_t k_sizes_secp256k1 = {
    32, 32, 64, 0, 0
};
static const crypto_alg_sizes_t k_sizes_ed25519 = {
    32, 32, 64, 0, 0
};
static const crypto_alg_sizes_t k_sizes_x25519 = {
    32, 32, 0, 0, 32
};
static const crypto_alg_sizes_t k_sizes_ml_dsa_65 = {
    4032, 1952, 3309, 0, 0
};
static const crypto_alg_sizes_t k_sizes_slh_dsa_128s = {
    64, 32, 7856, 0, 0
};
static const crypto_alg_sizes_t k_sizes_ml_kem_768 = {
    2400, 1184, 0, 1088, 32
};

const crypto_alg_sizes_t *crypto_alg_get_sizes(crypto_alg_t alg) {
    switch (alg) {
        case CRYPTO_ALG_SECP256K1:
            return &k_sizes_secp256k1;
        case CRYPTO_ALG_ED25519:
            return &k_sizes_ed25519;
        case CRYPTO_ALG_X25519:
            return &k_sizes_x25519;
        case CRYPTO_ALG_ML_DSA_65:
            return &k_sizes_ml_dsa_65;
        case CRYPTO_ALG_SLH_DSA_128S:
            return &k_sizes_slh_dsa_128s;
        case CRYPTO_ALG_ML_KEM_768:
            return &k_sizes_ml_kem_768;
        case CRYPTO_ALG_UNKNOWN:
        default:
            return NULL;
    }
}

crypto_alg_t crypto_alg_from_role(role_curve_t curve, role_purpose_t purpose) {
    switch (curve) {
        case CURVE_SECP256K1:
            if (purpose == PURPOSE_NOSTR) {
                return CRYPTO_ALG_SECP256K1;
            }
            return CRYPTO_ALG_UNKNOWN;
        case CURVE_ED25519:
            if (purpose == PURPOSE_SSH) {
                return CRYPTO_ALG_ED25519;
            }
            return CRYPTO_ALG_UNKNOWN;
        case CURVE_X25519:
            if (purpose == PURPOSE_AGE) {
                return CRYPTO_ALG_X25519;
            }
            return CRYPTO_ALG_UNKNOWN;
        case CURVE_ML_DSA_65:
            if (purpose == PURPOSE_PQ_SIG) {
                return CRYPTO_ALG_ML_DSA_65;
            }
            return CRYPTO_ALG_UNKNOWN;
        case CURVE_SLH_DSA_128S:
            if (purpose == PURPOSE_PQ_SIG) {
                return CRYPTO_ALG_SLH_DSA_128S;
            }
            return CRYPTO_ALG_UNKNOWN;
        case CURVE_ML_KEM_768:
            if (purpose == PURPOSE_PQ_KEM) {
                return CRYPTO_ALG_ML_KEM_768;
            }
            return CRYPTO_ALG_UNKNOWN;
        case CURVE_UNKNOWN:
        default:
            return CRYPTO_ALG_UNKNOWN;
    }
}

const char *crypto_alg_to_str(crypto_alg_t alg) {
    switch (alg) {
        case CRYPTO_ALG_SECP256K1:
            return "secp256k1";
        case CRYPTO_ALG_ED25519:
            return "ed25519";
        case CRYPTO_ALG_X25519:
            return "x25519";
        case CRYPTO_ALG_ML_DSA_65:
            return "ml-dsa-65";
        case CRYPTO_ALG_SLH_DSA_128S:
            return "slh-dsa-128s";
        case CRYPTO_ALG_ML_KEM_768:
            return "ml-kem-768";
        case CRYPTO_ALG_UNKNOWN:
        default:
            return NULL;
    }
}

crypto_alg_t crypto_alg_from_str(const char *s) {
    if (s == NULL) {
        return CRYPTO_ALG_UNKNOWN;
    }
    if (strcmp(s, "secp256k1") == 0) {
        return CRYPTO_ALG_SECP256K1;
    }
    if (strcmp(s, "ed25519") == 0) {
        return CRYPTO_ALG_ED25519;
    }
    if (strcmp(s, "x25519") == 0) {
        return CRYPTO_ALG_X25519;
    }
    if (strcmp(s, "ml-dsa-65") == 0) {
        return CRYPTO_ALG_ML_DSA_65;
    }
    if (strcmp(s, "slh-dsa-128s") == 0) {
        return CRYPTO_ALG_SLH_DSA_128S;
    }
    if (strcmp(s, "ml-kem-768") == 0) {
        return CRYPTO_ALG_ML_KEM_768;
    }
    return CRYPTO_ALG_UNKNOWN;
}

/* ===========================================================================
 * Phase 2: ed25519 + x25519 implementations using OpenSSL EVP API.
 *
 * OpenSSL 1.1.1+ provides EVP_PKEY_new_raw_private_key() and
 * EVP_PKEY_new_raw_public_key() which accept the raw 32-byte key material
 * directly — ideal for deterministic keygen from a derived seed.
 * ========================================================================== */

int crypto_ed25519_keygen_from_seed(const unsigned char *seed, size_t seed_len,
                                     unsigned char *priv_out, unsigned char *pub_out) {
    EVP_PKEY *pkey = NULL;
    size_t priv_len = 32;
    size_t pub_len = 32;

    if (seed == NULL || priv_out == NULL || pub_out == NULL || seed_len != 32) {
        return -1;
    }

    /* Create an EVP_PKEY from the raw 32-byte private key (seed). */
    pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, seed, seed_len);
    if (pkey == NULL) {
        return -1;
    }

    /* Extract the raw private key (should match the seed). */
    if (EVP_PKEY_get_raw_private_key(pkey, priv_out, &priv_len) != 1 || priv_len != 32) {
        EVP_PKEY_free(pkey);
        return -1;
    }

    /* Extract the raw public key (32 bytes). */
    if (EVP_PKEY_get_raw_public_key(pkey, pub_out, &pub_len) != 1 || pub_len != 32) {
        EVP_PKEY_free(pkey);
        return -1;
    }

    EVP_PKEY_free(pkey);
    return 0;
}

int crypto_ed25519_sign(const unsigned char *priv, size_t priv_len,
                         const unsigned char *msg, size_t msg_len,
                         unsigned char *sig_out, size_t *sig_out_len) {
    EVP_PKEY *pkey = NULL;
    EVP_MD_CTX *ctx = NULL;
    size_t sig_len = 64;

    if (priv == NULL || msg == NULL || sig_out == NULL || sig_out_len == NULL ||
        priv_len != 32) {
        return -1;
    }

    pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, priv, priv_len);
    if (pkey == NULL) {
        return -1;
    }

    ctx = EVP_MD_CTX_new();
    if (ctx == NULL) {
        EVP_PKEY_free(pkey);
        return -1;
    }

    /* ed25519 is single-shot: DigestSignInit with NULL md, then EVP_DigestSign. */
    if (EVP_DigestSignInit(ctx, NULL, NULL, NULL, pkey) != 1) {
        EVP_MD_CTX_free(ctx);
        EVP_PKEY_free(pkey);
        return -1;
    }

    if (EVP_DigestSign(ctx, sig_out, &sig_len, msg, msg_len) != 1) {
        EVP_MD_CTX_free(ctx);
        EVP_PKEY_free(pkey);
        return -1;
    }

    *sig_out_len = sig_len;

    EVP_MD_CTX_free(ctx);
    EVP_PKEY_free(pkey);
    return 0;
}

int crypto_ed25519_verify(const unsigned char *pub, size_t pub_len,
                           const unsigned char *msg, size_t msg_len,
                           const unsigned char *sig, size_t sig_len) {
    EVP_PKEY *pkey = NULL;
    EVP_MD_CTX *ctx = NULL;
    int rc;

    if (pub == NULL || msg == NULL || sig == NULL || pub_len != 32) {
        return -1;
    }

    pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, pub, pub_len);
    if (pkey == NULL) {
        return -1;
    }

    ctx = EVP_MD_CTX_new();
    if (ctx == NULL) {
        EVP_PKEY_free(pkey);
        return -1;
    }

    if (EVP_DigestVerifyInit(ctx, NULL, NULL, NULL, pkey) != 1) {
        EVP_MD_CTX_free(ctx);
        EVP_PKEY_free(pkey);
        return -1;
    }

    /* EVP_DigestVerify returns 1 for valid, 0 for invalid, <0 for error. */
    rc = EVP_DigestVerify(ctx, sig, sig_len, msg, msg_len);

    EVP_MD_CTX_free(ctx);
    EVP_PKEY_free(pkey);

    if (rc == 1) {
        return 0; /* valid */
    }
    if (rc == 0) {
        return 1; /* invalid signature */
    }
    return -1; /* error */
}

int crypto_x25519_keygen_from_seed(const unsigned char *seed, size_t seed_len,
                                    unsigned char *priv_out, unsigned char *pub_out) {
    EVP_PKEY *pkey = NULL;
    size_t priv_len = 32;
    size_t pub_len = 32;

    if (seed == NULL || priv_out == NULL || pub_out == NULL || seed_len != 32) {
        return -1;
    }

    pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL, seed, seed_len);
    if (pkey == NULL) {
        return -1;
    }

    if (EVP_PKEY_get_raw_private_key(pkey, priv_out, &priv_len) != 1 || priv_len != 32) {
        EVP_PKEY_free(pkey);
        return -1;
    }

    if (EVP_PKEY_get_raw_public_key(pkey, pub_out, &pub_len) != 1 || pub_len != 32) {
        EVP_PKEY_free(pkey);
        return -1;
    }

    EVP_PKEY_free(pkey);
    return 0;
}

int crypto_x25519_ecdh(const unsigned char *our_priv, size_t priv_len,
                        const unsigned char *peer_pub, size_t pub_len,
                        unsigned char *shared_out, size_t *shared_out_len) {
    EVP_PKEY *our_pkey = NULL;
    EVP_PKEY *peer_pkey = NULL;
    EVP_PKEY_CTX *ctx = NULL;
    size_t shared_len = 32;

    if (our_priv == NULL || peer_pub == NULL || shared_out == NULL ||
        shared_out_len == NULL || priv_len != 32 || pub_len != 32) {
        return -1;
    }

    our_pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL, our_priv, priv_len);
    if (our_pkey == NULL) {
        return -1;
    }

    peer_pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_X25519, NULL, peer_pub, pub_len);
    if (peer_pkey == NULL) {
        EVP_PKEY_free(our_pkey);
        return -1;
    }

    ctx = EVP_PKEY_CTX_new(our_pkey, NULL);
    if (ctx == NULL) {
        EVP_PKEY_free(peer_pkey);
        EVP_PKEY_free(our_pkey);
        return -1;
    }

    if (EVP_PKEY_derive_init(ctx) != 1) {
        EVP_PKEY_CTX_free(ctx);
        EVP_PKEY_free(peer_pkey);
        EVP_PKEY_free(our_pkey);
        return -1;
    }

    if (EVP_PKEY_derive_set_peer(ctx, peer_pkey) != 1) {
        EVP_PKEY_CTX_free(ctx);
        EVP_PKEY_free(peer_pkey);
        EVP_PKEY_free(our_pkey);
        return -1;
    }

    if (EVP_PKEY_derive(ctx, shared_out, &shared_len) != 1 || shared_len != 32) {
        EVP_PKEY_CTX_free(ctx);
        EVP_PKEY_free(peer_pkey);
        EVP_PKEY_free(our_pkey);
        return -1;
    }

    *shared_out_len = shared_len;

    EVP_PKEY_CTX_free(ctx);
    EVP_PKEY_free(peer_pkey);
    EVP_PKEY_free(our_pkey);
    return 0;
}

/* ===========================================================================
 * SLIP-0010 / BIP-32 seed derivation for ed25519 and x25519.
 *
 * SLIP-0010 requires ALL derivation levels to be hardened. The HMAC-SHA512
 * hardened child derivation is:
 *   I = HMAC-SHA512(key = cpar, data = 0x00 || ser256(kpar) || ser32(i))
 *   IL = first 32 bytes (becomes child private key — NOT added to parent)
 *   IR = last 32 bytes (becomes child chain code)
 *
 * For ed25519/x25519, the SLIP-0010 spec says the child key IS IL directly
 * (no parent addition, since ed25519 scalars don't support that operation).
 *
 * We reuse nostr_bip39_mnemonic_to_seed() for the BIP-39 seed and implement
 * the SLIP-0010 hardened-only derivation ourselves using OpenSSL HMAC-SHA512.
 * ========================================================================== */

/* Parse a BIP-32 path string like "m/44'/102001'/0'/0'/0'" into an array
 * of uint32_t indices. Returns the number of indices parsed, or -1 on error.
 * All components must be hardened (>= 0x80000000) for SLIP-0010. */
static int slip10_parse_path(const char *path, uint32_t *out, int out_max) {
    int count = 0;
    const char *p;

    if (path == NULL || out == NULL || out_max <= 0) {
        return -1;
    }

    /* Skip leading "m" or "m/" */
    p = path;
    if (*p == 'm' || *p == 'M') {
        p++;
        if (*p == '/') {
            p++;
        } else if (*p != '\0') {
            return -1;
        }
    }

    while (*p != '\0' && count < out_max) {
        unsigned long val = 0;
        int hardened = 0;

        /* Parse the number */
        if (*p < '0' || *p > '9') {
            return -1;
        }
        while (*p >= '0' && *p <= '9') {
            val = val * 10 + (unsigned long)(*p - '0');
            p++;
        }

        /* Check for hardened marker */
        if (*p == '\'' || *p == 'h' || *p == 'H') {
            hardened = 1;
            p++;
        }

        /* Must be followed by '/' or end of string */
        if (*p == '/') {
            p++;
        } else if (*p != '\0') {
            return -1;
        }

        if (val >= 0x80000000UL) {
            return -1; /* index too large */
        }

        out[count] = (uint32_t)val | (hardened ? 0x80000000UL : 0UL);
        count++;
    }

    return count;
}

/* SLIP-0010 hardened child key derivation.
 * parent_priv: 32-byte parent private key
 * parent_chain: 32-byte parent chain code
 * index: child index (must have hardened bit set for SLIP-0010)
 * child_priv: 32-byte output child private key
 * child_chain: 32-byte output child chain code
 * Returns 0 on success, -1 on error. */
static int slip10_ckd_hardened(const unsigned char *parent_priv,
                                const unsigned char *parent_chain,
                                uint32_t index,
                                unsigned char *child_priv,
                                unsigned char *child_chain) {
    unsigned char data[37];
    unsigned char hmac_out[64];
    unsigned int hmac_len = 64;

    /* data = 0x00 || ser256(kpar) || ser32(i) */
    data[0] = 0x00;
    memcpy(data + 1, parent_priv, 32);
    data[33] = (index >> 24) & 0xFF;
    data[34] = (index >> 16) & 0xFF;
    data[35] = (index >> 8) & 0xFF;
    data[36] = index & 0xFF;

    if (HMAC(EVP_sha512(), parent_chain, 32, data, sizeof(data), hmac_out, &hmac_len) == NULL) {
        return -1;
    }
    if (hmac_len != 64) {
        return -1;
    }

    /* IL = first 32 bytes = child private key (SLIP-0010: no addition) */
    memcpy(child_priv, hmac_out, 32);
    /* IR = last 32 bytes = child chain code */
    memcpy(child_chain, hmac_out + 32, 32);

    OPENSSL_cleanse(hmac_out, sizeof(hmac_out));
    return 0;
}

int crypto_derive_seed_from_mnemonic(const char *mnemonic, const char *path,
                                      unsigned char *seed_out, size_t seed_out_len) {
    unsigned char bip39_seed[64];
    unsigned char master_hmac[64];
    unsigned char cur_priv[32];
    unsigned char cur_chain[32];
    unsigned char child_priv[32];
    unsigned char child_chain[32];
    uint32_t indices[16];
    int index_count;
    int i;
    unsigned int hmac_len;
    int rc = -1;

    if (mnemonic == NULL || path == NULL || seed_out == NULL || seed_out_len < 32) {
        return -1;
    }

    /* 1. Convert mnemonic to BIP-39 seed (64 bytes). */
    if (nostr_bip39_mnemonic_to_seed(mnemonic, "", bip39_seed, sizeof(bip39_seed)) != 0) {
        return -1;
    }

    /* 2. Derive SLIP-0010 master key: I = HMAC-SHA512("ed25519 seed", bip39_seed)
     *    (SLIP-0010 uses "ed25519 seed" as the key, not "Bitcoin seed"). */
    hmac_len = 64;
    if (HMAC(EVP_sha512(), (const unsigned char *)"ed25519 seed", 12,
             bip39_seed, sizeof(bip39_seed), master_hmac, &hmac_len) == NULL) {
        OPENSSL_cleanse(bip39_seed, sizeof(bip39_seed));
        return -1;
    }
    OPENSSL_cleanse(bip39_seed, sizeof(bip39_seed));
    if (hmac_len != 64) {
        goto cleanup;
    }

    /* master: IL = priv, IR = chain */
    memcpy(cur_priv, master_hmac, 32);
    memcpy(cur_chain, master_hmac + 32, 32);
    OPENSSL_cleanse(master_hmac, sizeof(master_hmac));

    /* 3. Parse the derivation path. */
    index_count = slip10_parse_path(path, indices, 16);
    if (index_count < 0) {
        goto cleanup;
    }

    /* 4. Walk the path with hardened CKD. */
    for (i = 0; i < index_count; i++) {
        /* SLIP-0010 requires all levels hardened. */
        if ((indices[i] & 0x80000000UL) == 0) {
            goto cleanup;
        }

        if (slip10_ckd_hardened(cur_priv, cur_chain, indices[i],
                                 child_priv, child_chain) != 0) {
            goto cleanup;
        }

        memcpy(cur_priv, child_priv, 32);
        memcpy(cur_chain, child_chain, 32);
    }

    /* 5. The final 32-byte private key is the seed. */
    memcpy(seed_out, cur_priv, 32);
    rc = 0;

cleanup:
    OPENSSL_cleanse(master_hmac, sizeof(master_hmac));
    OPENSSL_cleanse(cur_priv, sizeof(cur_priv));
    OPENSSL_cleanse(cur_chain, sizeof(cur_chain));
    OPENSSL_cleanse(child_priv, sizeof(child_priv));
    OPENSSL_cleanse(child_chain, sizeof(child_chain));
    return rc;
}

/* ===========================================================================
 * Phase 3: ML-DSA-65 (FIPS 204) — post-quantum digital signatures.
 *
 * Wraps the vendored PQClean ML-DSA-65 implementation. Key generation is
 * deterministic: the DRBG is seeded from a mnemonic-derived 32-byte seed,
 * then ML-DSA-65 keygen is called (which calls randombytes() internally,
 * which is hooked to our DRBG). Signing is deterministic per FIPS 204
 * (no randombytes() call during signing).
 *
 * Key sizes (FIPS 204, ML-DSA-65):
 *   Public key:  1952 bytes
 *   Secret key:  4032 bytes
 *   Signature:   3309 bytes
 * ========================================================================== */

/* DRBG functions — defined in src/pq_drbg.c */
void pq_drbg_init(const unsigned char *seed, size_t seed_len);
int  pq_drbg_randombytes(unsigned char *buf, size_t len);
void pq_drbg_zeroize(void);

int crypto_ml_dsa_65_keygen_from_seed(const unsigned char *seed, size_t seed_len,
                                       unsigned char *priv_out, unsigned char *pub_out) {
    if (seed == NULL || priv_out == NULL || pub_out == NULL || seed_len != 32) {
        return -1;
    }

    /* Seed the DRBG with the mnemonic-derived seed.
     * This makes the keygen deterministic: same seed → same keypair. */
    pq_drbg_init(seed, seed_len);

    /* Call ML-DSA-65 keygen. This calls randombytes() internally,
     * which is hooked to our DRBG. */
    if (crypto_sign_keypair(pub_out, priv_out) != 0) {
        pq_drbg_zeroize();
        return -1;
    }

    /* Zeroize the DRBG state after keygen (sensitive seed material). */
    pq_drbg_zeroize();

    return 0;
}

int crypto_ml_dsa_65_sign(const unsigned char *priv, size_t priv_len,
                           const unsigned char *msg, size_t msg_len,
                           unsigned char *sig_out, size_t *sig_out_len) {
    size_t sig_len = 0;

    if (priv == NULL || msg == NULL || sig_out == NULL || sig_out_len == NULL ||
        priv_len != ML_DSA_65_CRYPTO_SECRETKEYBYTES) {
        return -1;
    }

    /* ML-DSA-65 signing is deterministic (FIPS 204) — no DRBG needed. */
    if (crypto_sign(sig_out, &sig_len, msg, msg_len, priv) != 0) {
        return -1;
    }

    *sig_out_len = sig_len;
    return 0;
}

int crypto_ml_dsa_65_verify(const unsigned char *pub, size_t pub_len,
                             const unsigned char *msg, size_t msg_len,
                             const unsigned char *sig, size_t sig_len) {
    if (pub == NULL || msg == NULL || sig == NULL ||
        pub_len != ML_DSA_65_CRYPTO_PUBLICKEYBYTES) {
        return -1;
    }

    /* Use detached signature verification: pass sig as sm, and msg separately.
     * crypto_sign_open expects sm = sig (when smlen == CRYPTO_BYTES, it's detached).
     * We pass m = msg (input), mlen = msg_len (input). */
    {
        size_t mlen = msg_len;
        int rc = crypto_sign_open((unsigned char *)msg, &mlen,
                                   sig, sig_len, pub);
        if (rc == 0) {
            return 0;  /* valid */
        }
        return 1;  /* invalid */
    }
}

/* ===========================================================================
 * Phase 4: SLH-DSA-128s (FIPS 205) — hash-based post-quantum signatures.
 *
 * Wraps the vendored PQClean SLH-DSA-128s implementation. Key generation is
 * deterministic: the DRBG is seeded from a mnemonic-derived 32-byte seed,
 * then SLH-DSA-128s keygen is called (which calls randombytes() internally,
 * which is hooked to our DRBG). Signing uses randombytes() for the opt_rand
 * value — with a deterministic PRNG, signing is also deterministic.
 *
 * Key sizes (FIPS 205, SLH-DSA-128s):
 *   Public key:  32 bytes
 *   Secret key:  64 bytes
 *   Signature:   7856 bytes
 * ========================================================================== */

int crypto_slh_dsa_128s_keygen_from_seed(const unsigned char *seed, size_t seed_len,
                                           unsigned char *priv_out, unsigned char *pub_out) {
    if (seed == NULL || priv_out == NULL || pub_out == NULL || seed_len != 32) {
        return -1;
    }

    /* Seed the DRBG with the mnemonic-derived seed.
     * This makes the keygen deterministic: same seed → same keypair. */
    pq_drbg_init(seed, seed_len);

    /* Call SLH-DSA-128s keygen. This calls randombytes() internally,
     * which is hooked to our DRBG. */
    if (slh_dsa_128s_crypto_sign_keypair(pub_out, priv_out) != 0) {
        pq_drbg_zeroize();
        return -1;
    }

    /* Zeroize the DRBG state after keygen (sensitive seed material). */
    pq_drbg_zeroize();

    return 0;
}

int crypto_slh_dsa_128s_sign(const unsigned char *priv, size_t priv_len,
                               const unsigned char *msg, size_t msg_len,
                               unsigned char *sig_out, size_t *sig_out_len) {
    size_t sig_len = 0;

    if (priv == NULL || msg == NULL || sig_out == NULL || sig_out_len == NULL ||
        priv_len != SLH_DSA_128S_CRYPTO_SECRETKEYBYTES) {
        return -1;
    }

    /* SLH-DSA-128s signing uses randombytes() for the opt_rand value.
     * With our deterministic DRBG (if seeded), signing is deterministic.
     * If the DRBG is not seeded, randombytes() will fail. We seed it
     * from the secret key's SK.prf to make signing deterministic. */
    {
        /* Seed the DRBG from SK.prf (bytes 16..31 of the secret key) to
         * make signing deterministic. This is not the standard approach
         * (which uses a separate RNG), but it ensures deterministic
         * signing which is what we need for mnemonic-recoverable keys. */
        const unsigned char *sk_prf = priv + SLH_DSA_128S_N;
        pq_drbg_init(sk_prf, SLH_DSA_128S_N);
    }

    if (slh_dsa_128s_crypto_sign(sig_out, &sig_len, msg, msg_len, priv) != 0) {
        pq_drbg_zeroize();
        return -1;
    }

    pq_drbg_zeroize();

    *sig_out_len = sig_len;
    return 0;
}

int crypto_slh_dsa_128s_verify(const unsigned char *pub, size_t pub_len,
                                 const unsigned char *msg, size_t msg_len,
                                 const unsigned char *sig, size_t sig_len) {
    if (pub == NULL || msg == NULL || sig == NULL ||
        pub_len != SLH_DSA_128S_CRYPTO_PUBLICKEYBYTES) {
        return -1;
    }

    /* Detached signature verification: pass sig as sm, msg separately.
     * When smlen == CRYPTO_BYTES, crypto_sign_open treats it as detached
     * and uses m/mlen as the message. */
    {
        size_t mlen = msg_len;
        int rc = slh_dsa_128s_crypto_sign_open((unsigned char *)msg, &mlen,
                                                 sig, sig_len, pub);
        if (rc == 0) {
            return 0;  /* valid */
        }
        return 1;  /* invalid */
    }
}

/* ===========================================================================
 * Phase 5: ML-KEM-768 (FIPS 203) — post-quantum key encapsulation mechanism.
 *
 * Wraps the vendored PQClean ML-KEM-768 implementation. Key generation is
 * deterministic: the DRBG is seeded from a mnemonic-derived 32-byte seed,
 * then ML-KEM-768 keygen is called (which calls randombytes() internally,
 * which is hooked to our DRBG). Encapsulation uses real randomness (OpenSSL
 * RAND_bytes) since each encapsulation must produce a different ciphertext.
 * Decapsulation is deterministic.
 *
 * Key sizes (FIPS 203, ML-KEM-768):
 *   Public key:   1184 bytes
 *   Secret key:   2400 bytes
 *   Ciphertext:   1088 bytes
 *   Shared secret: 32 bytes
 * ========================================================================== */

int crypto_ml_kem_768_keygen_from_seed(const unsigned char *seed, size_t seed_len,
                                         unsigned char *priv_out, unsigned char *pub_out) {
    if (seed == NULL || priv_out == NULL || pub_out == NULL || seed_len != 32) {
        return -1;
    }

    /* Seed the DRBG with the mnemonic-derived seed for deterministic keygen. */
    pq_drbg_init(seed, seed_len);

    /* Call ML-KEM-768 keygen. This calls randombytes() internally,
     * which is hooked to our DRBG. */
    if (crypto_kem_keypair(pub_out, priv_out) != 0) {
        pq_drbg_zeroize();
        return -1;
    }

    pq_drbg_zeroize();
    return 0;
}

int crypto_ml_kem_768_encaps(const unsigned char *pub, size_t pub_len,
                               unsigned char *ct_out, unsigned char *ss_out) {
    unsigned char real_seed[32];

    if (pub == NULL || ct_out == NULL || ss_out == NULL ||
        pub_len != ML_KEM_768_CRYPTO_PUBLICKEYBYTES) {
        return -1;
    }

    /* Encapsulation uses real randomness (not deterministic).
     * We seed the DRBG with real randomness from OpenSSL, so each
     * encapsulation produces a different ciphertext. The crypto_kem_enc
     * function calls randombytes() which is hooked to our DRBG. */
    if (RAND_bytes(real_seed, 32) != 1) {
        return -1;
    }
    pq_drbg_init(real_seed, 32);
    OPENSSL_cleanse(real_seed, sizeof(real_seed));

    if (crypto_kem_enc(ct_out, ss_out, pub) != 0) {
        pq_drbg_zeroize();
        return -1;
    }

    pq_drbg_zeroize();
    return 0;
}

int crypto_ml_kem_768_decaps(const unsigned char *priv, size_t priv_len,
                               const unsigned char *ct, size_t ct_len,
                               unsigned char *ss_out) {
    if (priv == NULL || ct == NULL || ss_out == NULL ||
        priv_len != ML_KEM_768_CRYPTO_SECRETKEYBYTES ||
        ct_len != ML_KEM_768_CRYPTO_CIPHERTEXTBYTES) {
        return -1;
    }

    /* Decapsulation is deterministic — no randomness needed. */
    if (crypto_kem_dec(ss_out, ct, priv) != 0) {
        return -1;
    }

    return 0;
}

/* ===========================================================================
 * Phase 6: secp256k1 generic Schnorr (BIP-340) and ECDSA sign/verify.
 *
 * These are used by the algorithm-based `sign` and `verify` verbs to sign
 * arbitrary bytes (not Nostr events). The message is hashed with SHA-256
 * before signing, matching the Nostr event-signing convention.
 *
 * Schnorr uses libsecp256k1's schnorrsig API with x-only pubkeys (BIP-340).
 * ECDSA uses libsecp256k1's ecdsa_sign with compact 64-byte (r||s) output.
 * ========================================================================== */

/* Thread-safe context creation. We use secp256k1_context_create with
 * SECP256K1_CONTEXT_NONE since we only sign/verify (no ecdh needed here).
 * The context is created per-call to avoid global state and threading
 * concerns; for a signer daemon this is acceptable since signing is not
 * a hot path. */
static secp256k1_context *secp_ctx(void) {
    return secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
}

int crypto_secp256k1_schnorr_sign(const unsigned char *priv, size_t priv_len,
                                    const unsigned char *msg, size_t msg_len,
                                    unsigned char *sig_out, size_t *sig_out_len) {
    secp256k1_context *ctx = NULL;
    secp256k1_keypair keypair;
    unsigned char msg_hash[32];
    unsigned char aux_rand[32] = {0};

    if (priv == NULL || msg == NULL || sig_out == NULL || sig_out_len == NULL ||
        priv_len != 32) {
        return -1;
    }

    /* SHA-256 the message (Nostr convention). */
    if (nostr_sha256(msg, msg_len, msg_hash) != 0) {
        return -1;
    }

    ctx = secp_ctx();
    if (ctx == NULL) {
        secure_memzero(msg_hash, sizeof(msg_hash));
        return -1;
    }

    /* Load private key into a keypair. */
    if (secp256k1_keypair_create(ctx, &keypair, priv) != 1) {
        secp256k1_context_destroy(ctx);
        secure_memzero(msg_hash, sizeof(msg_hash));
        return -1;
    }

    /* Sign with NULL aux_rand (deterministic via libsecp256k1's RFC 6979). */
    (void)aux_rand;
    if (secp256k1_schnorrsig_sign32(ctx, sig_out, msg_hash, &keypair, NULL) != 1) {
        secp256k1_context_destroy(ctx);
        secure_memzero(msg_hash, sizeof(msg_hash));
        secure_memzero(&keypair, sizeof(keypair));
        return -1;
    }

    *sig_out_len = 64;

    secp256k1_context_destroy(ctx);
    secure_memzero(msg_hash, sizeof(msg_hash));
    secure_memzero(&keypair, sizeof(keypair));
    return 0;
}

int crypto_secp256k1_schnorr_verify(const unsigned char *pub, size_t pub_len,
                                      const unsigned char *msg, size_t msg_len,
                                      const unsigned char *sig, size_t sig_len) {
    secp256k1_context *ctx = NULL;
    secp256k1_xonly_pubkey xonly_pub;
    unsigned char msg_hash[32];
    int rc;

    if (pub == NULL || msg == NULL || sig == NULL || pub_len != 32 || sig_len != 64) {
        return -1;
    }

    if (nostr_sha256(msg, msg_len, msg_hash) != 0) {
        return -1;
    }

    ctx = secp_ctx();
    if (ctx == NULL) {
        secure_memzero(msg_hash, sizeof(msg_hash));
        return -1;
    }

    if (secp256k1_xonly_pubkey_parse(ctx, &xonly_pub, pub) != 1) {
        secp256k1_context_destroy(ctx);
        secure_memzero(msg_hash, sizeof(msg_hash));
        return -1;
    }

    rc = secp256k1_schnorrsig_verify(ctx, sig, msg_hash, 32, &xonly_pub);

    secp256k1_context_destroy(ctx);
    secure_memzero(msg_hash, sizeof(msg_hash));
    secure_memzero(&xonly_pub, sizeof(xonly_pub));

    if (rc == 1) {
        return 0;  /* valid */
    }
    return 1;  /* invalid */
}

int crypto_secp256k1_ecdsa_sign(const unsigned char *priv, size_t priv_len,
                                  const unsigned char *msg, size_t msg_len,
                                  unsigned char *sig_out, size_t *sig_out_len) {
    secp256k1_context *ctx = NULL;
    secp256k1_ecdsa_signature sig;
    unsigned char msg_hash[32];

    if (priv == NULL || msg == NULL || sig_out == NULL || sig_out_len == NULL ||
        priv_len != 32) {
        return -1;
    }

    if (nostr_sha256(msg, msg_len, msg_hash) != 0) {
        return -1;
    }

    ctx = secp_ctx();
    if (ctx == NULL) {
        secure_memzero(msg_hash, sizeof(msg_hash));
        return -1;
    }

    if (secp256k1_ecdsa_sign(ctx, &sig, msg_hash, priv, NULL, NULL) != 1) {
        secp256k1_context_destroy(ctx);
        secure_memzero(msg_hash, sizeof(msg_hash));
        return -1;
    }

    /* Serialize as compact 64-byte (r||s). */
    secp256k1_ecdsa_signature_serialize_compact(ctx, sig_out, &sig);
    *sig_out_len = 64;

    secp256k1_context_destroy(ctx);
    secure_memzero(msg_hash, sizeof(msg_hash));
    secure_memzero(&sig, sizeof(sig));
    return 0;
}

int crypto_secp256k1_ecdsa_verify(const unsigned char *pub, size_t pub_len,
                                    const unsigned char *msg, size_t msg_len,
                                    const unsigned char *sig, size_t sig_len) {
    secp256k1_context *ctx = NULL;
    secp256k1_ecdsa_signature sig_secp;
    secp256k1_pubkey pub_secp;
    unsigned char compressed_pub[33];
    unsigned char msg_hash[32];
    int rc;

    if (pub == NULL || msg == NULL || sig == NULL || pub_len != 32 || sig_len != 64) {
        return -1;
    }

    if (nostr_sha256(msg, msg_len, msg_hash) != 0) {
        return -1;
    }

    /* The x-only pubkey (32 bytes) needs a 0x02 prefix to become a
     * compressed pubkey (33 bytes) that secp256k1_pubkey_parse accepts. */
    compressed_pub[0] = 0x02;
    memcpy(compressed_pub + 1, pub, 32);

    ctx = secp_ctx();
    if (ctx == NULL) {
        secure_memzero(msg_hash, sizeof(msg_hash));
        return -1;
    }

    if (secp256k1_ec_pubkey_parse(ctx, &pub_secp, compressed_pub, 33) != 1) {
        /* Try with 0x03 prefix (odd y) — but for x-only pubkeys from
         * Schnorr, the y is implicitly even (0x02). If that fails, the
         * pubkey is invalid. */
        secp256k1_context_destroy(ctx);
        secure_memzero(msg_hash, sizeof(msg_hash));
        return -1;
    }

    if (secp256k1_ecdsa_signature_parse_compact(ctx, &sig_secp, sig) != 1) {
        secp256k1_context_destroy(ctx);
        secure_memzero(msg_hash, sizeof(msg_hash));
        secure_memzero(&pub_secp, sizeof(pub_secp));
        return -1;
    }

    rc = secp256k1_ecdsa_verify(ctx, &sig_secp, msg_hash, &pub_secp);

    secp256k1_context_destroy(ctx);
    secure_memzero(msg_hash, sizeof(msg_hash));
    secure_memzero(&pub_secp, sizeof(pub_secp));
    secure_memzero(&sig_secp, sizeof(sig_secp));

    if (rc == 1) {
        return 0;  /* valid */
    }
    return 1;  /* invalid */
}
