/* * pq_sign_example.c — connect to a running n_signer over its abstract UNIX * socket and demonstrate post-quantum signing with ML-DSA-65. * * The example: * 1. Sends a get_public_key request for an ML-DSA-65 role ("pq_sig"). * 2. Prints the structured public key (algorithm, public_key, key_id). * 3. Sends a sign_data request with a test message. * 4. Prints the signature (hex) and algorithm. * * Prerequisites: * - n_signer must be running with a role configured for purpose=pq-sig, * curve=ml-dsa-65, named "pq_sig" (or pass the role name as the 2nd arg). * - A mnemonic must be loaded in the signer. * * Usage: ./pq_sign_example [socket_name] [role_name] * * Default socket_name: nsigner * Default role_name: pq_sig */ #include #include #include #include "nostr_common.h" #include "nsigner_transport.h" #include "nsigner_client.h" #include "../cjson/cJSON.h" static int get_structured_pubkey(nsigner_client_t *client, const char *role, cJSON **out_obj) { cJSON *params = NULL; cJSON *opts = NULL; cJSON *result = NULL; cJSON *parsed = NULL; int rc = -1; *out_obj = NULL; params = cJSON_CreateArray(); if (params == NULL) return -1; opts = cJSON_CreateObject(); if (opts == NULL) { cJSON_Delete(params); return -1; } cJSON_AddStringToObject(opts, "algorithm", "ml-dsa-65"); cJSON_AddNumberToObject(opts, "index", 0); cJSON_AddItemToArray(params, opts); opts = NULL; if (nsigner_client_call(client, "get_public_key", params, &result) != NOSTR_SUCCESS) { cJSON_Delete(params); return -1; } params = NULL; /* result is a cJSON string containing the serialized structured object. */ if (cJSON_IsString(result)) { parsed = cJSON_Parse(result->valuestring); if (parsed != NULL && cJSON_IsObject(parsed)) { *out_obj = parsed; parsed = NULL; rc = 0; } } cJSON_Delete(parsed); cJSON_Delete(result); cJSON_Delete(params); return rc; } static char *sign_data(nsigner_client_t *client, const char *role, const char *msg_hex) { cJSON *params = NULL; cJSON *opts = NULL; cJSON *result = NULL; char *sig_hex = NULL; params = cJSON_CreateArray(); if (params == NULL) return NULL; cJSON_AddItemToArray(params, cJSON_CreateString(msg_hex)); opts = cJSON_CreateObject(); if (opts == NULL) { cJSON_Delete(params); return NULL; } cJSON_AddStringToObject(opts, "algorithm", "ml-dsa-65"); cJSON_AddNumberToObject(opts, "index", 0); cJSON_AddItemToArray(params, opts); opts = NULL; if (nsigner_client_call(client, "sign", params, &result) != NOSTR_SUCCESS) { cJSON_Delete(params); return NULL; } params = NULL; /* result is a string containing {"signature":"","algorithm":""} */ if (cJSON_IsString(result)) { cJSON *parsed = cJSON_Parse(result->valuestring); if (parsed != NULL) { cJSON *sig_item = cJSON_GetObjectItemCaseSensitive(parsed, "signature"); if (cJSON_IsString(sig_item)) { sig_hex = strdup(sig_item->valuestring); } cJSON_Delete(parsed); } } cJSON_Delete(result); cJSON_Delete(params); return sig_hex; } int main(int argc, char **argv) { const char *socket_name = "nsigner"; const char *role = "pq_sig"; /* "hello post-quantum world" in hex */ const char *msg_hex = "68656c6c6f20706f73742d7175616e74756d20776f726c64"; nsigner_transport_t *transport = NULL; nsigner_client_t *client = NULL; cJSON *pubkey_obj = NULL; char *sig_hex = NULL; int rc = 1; if (argc > 1 && argv[1] != NULL && argv[1][0] != '\0') { socket_name = argv[1]; } if (argc > 2 && argv[2] != NULL && argv[2][0] != '\0') { role = argv[2]; } if (nostr_init() != NOSTR_SUCCESS) { fprintf(stderr, "failed to initialize crypto subsystem\n"); return 1; } transport = nsigner_transport_open_unix(socket_name, 10000); if (transport == NULL) { fprintf(stderr, "connect failed: cannot open unix transport @%s\n", socket_name); goto cleanup; } client = nsigner_client_new(transport); if (client == NULL) { fprintf(stderr, "connect failed: cannot create nsigner client\n"); transport->close(transport); goto cleanup; } transport = NULL; printf("=== PQ Sign Example (ML-DSA-65) ===\n"); printf("socket: %s\n", socket_name); printf("role: %s\n", role); printf("\n"); /* 1. Get the structured public key. */ if (get_structured_pubkey(client, role, &pubkey_obj) != 0 || pubkey_obj == NULL) { fprintf(stderr, "get_public_key failed: %s\n", nsigner_client_last_error(client)); goto cleanup; } { cJSON *alg_item = cJSON_GetObjectItemCaseSensitive(pubkey_obj, "algorithm"); cJSON *pk_item = cJSON_GetObjectItemCaseSensitive(pubkey_obj, "public_key"); cJSON *kid_item = cJSON_GetObjectItemCaseSensitive(pubkey_obj, "key_id"); printf("Public Key:\n"); printf(" algorithm: %s\n", (cJSON_IsString(alg_item)) ? alg_item->valuestring : "?"); printf(" key_id: %s\n", (cJSON_IsString(kid_item)) ? kid_item->valuestring : "?"); if (cJSON_IsString(pk_item)) { /* ML-DSA-65 public key is 3904 hex chars — print length + prefix. */ printf(" pub_len: %zu hex chars (%zu bytes)\n", strlen(pk_item->valuestring), strlen(pk_item->valuestring) / 2); printf(" pub_head: %.64s...\n", pk_item->valuestring); } else { printf(" public_key: (missing)\n"); } } printf("\n"); /* 2. Sign a test message. */ printf("Signing message (hex): %s\n", msg_hex); sig_hex = sign_data(client, role, msg_hex); if (sig_hex == NULL) { fprintf(stderr, "sign_data failed: %s\n", nsigner_client_last_error(client)); goto cleanup; } printf("Signature:\n"); printf(" sig_len: %zu hex chars (%zu bytes)\n", strlen(sig_hex), strlen(sig_hex) / 2); printf(" sig_head: %.64s...\n", sig_hex); printf("\n"); printf("To verify externally, use ML-DSA-65 (FIPS 204) verify with the\n"); printf("public key above, the message, and this signature.\n"); rc = 0; cleanup: free(sig_hex); cJSON_Delete(pubkey_obj); nsigner_client_free(client); nostr_cleanup(); return rc; }