/* * demo_c99.c — comprehensive C99 demo for connecting to a running n_signer * via Qubes qrexec and performing all three core operations: * * 1. get_public_key — retrieve a Nostr public key by nostr_index * 2. nostr_sign_event — sign a Nostr event (kind 1 text note) * 3. nostr_nip44_encrypt — encrypt a message to a peer (and decrypt it back) * * Note: nostr_mine_event (NIP-13 PoW) is also available via the JSON-RPC interface. * See demo_javascript.js and demo_python.py for nostr_mine_event usage examples. * The high-level nostr_signer API does not yet wrap nostr_mine_event. * * This uses the high-level nostr_signer API from nostr_core_lib: * - nostr_signer_nsigner_qrexec() — qrexec transport (no network) * - nostr_signer_nsigner_set_nostr_index() — select key by NIP-06 index * - nostr_signer_get_public_key() — get pubkey * - nostr_signer_sign_event() — sign an event * - nostr_signer_nip44_encrypt() — encrypt * - nostr_signer_nip44_decrypt() — decrypt * * Prerequisites: * - n_signer running in the target qube with --bridge-source-trusted * - qubes.NsignerRpc service installed in the target qube * - dom0 qrexec policy allowing this qube to call the service * * Build (from n_signer repo root): * make examples * * Usage: * ./build/demo_c99 [nostr_index] * ./build/demo_c99 nostr_signer 1 * * If no nostr_index is given, defaults to 0. */ #include #include #include #include #include #include "nostr_common.h" #include "nostr_signer.h" #include "nip019.h" #include "../cjson/cJSON.h" /* Helper: convert 32-byte hex pubkey to bech32 npub */ static int hex_to_npub(const char *hex, char *out_npub, size_t out_sz) { unsigned char bytes[32]; int i; if (strlen(hex) != 64) { return -1; } for (i = 0; i < 32; i++) { unsigned int byte; if (sscanf(hex + 2 * i, "%2x", &byte) != 1) { return -1; } bytes[i] = (unsigned char)byte; } return nostr_key_to_bech32(bytes, "npub", out_npub); } /* Helper: print an error with a human-readable description */ static void print_error(const char *operation, int rc) { const char *desc = "unknown error"; switch (rc) { case NOSTR_ERROR_INVALID_INPUT: desc = "invalid input"; break; case NOSTR_ERROR_CRYPTO_FAILED: desc = "crypto operation failed"; break; case NOSTR_ERROR_IO_FAILED: desc = "I/O failed (transport error)"; break; case NOSTR_ERROR_NETWORK_FAILED: desc = "network failed"; break; case NOSTR_ERROR_NSIGNER_POLICY_DENIED: desc = "policy denied (caller not approved at signer terminal)"; break; case NOSTR_ERROR_NSIGNER_INDEX_NOT_ALLOWED: desc = "index not in signer's whitelist"; break; default: /* Try to print the numeric code */ fprintf(stderr, " %s failed: error code %d\n", operation, rc); return; } fprintf(stderr, " %s failed: %s (code %d)\n", operation, desc, rc); } /* * Demo 1: Get a public key by nostr_index. * Returns the hex pubkey in `out_hex` (must be 65 bytes). */ static int demo_get_public_key(nostr_signer_t *signer, int nostr_index, char *out_hex, size_t hex_sz) { char npub[128]; int rc; printf("\n=== Demo 1: get_public_key (nostr_index=%d) ===\n", nostr_index); rc = nostr_signer_get_public_key(signer, out_hex); if (rc != NOSTR_SUCCESS) { print_error("get_public_key", rc); return rc; } if (hex_to_npub(out_hex, npub, sizeof(npub)) == 0) { printf(" pubkey hex: %s\n", out_hex); printf(" npub: %s\n", npub); } else { printf(" pubkey hex: %s\n", out_hex); printf(" (npub conversion failed)\n"); } return NOSTR_SUCCESS; } /* * Demo 2: Sign a Nostr event (kind 1 text note). * The signed event JSON is printed. */ static int demo_sign_event(nostr_signer_t *signer, const char *pubkey_hex) { cJSON *unsigned_event = NULL; cJSON *signed_event = NULL; char *signed_json = NULL; int rc; printf("\n=== Demo 2: nostr_sign_event (kind 1 text note) ===\n"); /* Build an unsigned Nostr event (kind 1 text note) */ unsigned_event = cJSON_CreateObject(); if (unsigned_event == NULL) { fprintf(stderr, " failed to create event JSON\n"); return NOSTR_ERROR_MEMORY_FAILED; } cJSON_AddNumberToObject(unsigned_event, "kind", 1); cJSON_AddStringToObject(unsigned_event, "content", "Hello from n_signer C99 demo!"); cJSON_AddNumberToObject(unsigned_event, "created_at", (int)time(NULL)); /* tags: empty array */ cJSON_AddItemToObject(unsigned_event, "tags", cJSON_CreateArray()); /* pubkey: the signer will fill this in, but we include it for completeness */ cJSON_AddStringToObject(unsigned_event, "pubkey", pubkey_hex); printf(" Unsigned event:\n"); { char *tmp = cJSON_PrintUnformatted(unsigned_event); if (tmp) { printf(" %s\n", tmp); free(tmp); } } /* Sign it */ rc = nostr_signer_sign_event(signer, unsigned_event, &signed_event); if (rc != NOSTR_SUCCESS) { print_error("nostr_nostr_sign_event", rc); cJSON_Delete(unsigned_event); return rc; } /* Print the signed event */ signed_json = cJSON_Print(signed_event); if (signed_json) { printf(" Signed event:\n"); printf(" %s\n", signed_json); free(signed_json); } /* Extract and show the signature and event id */ { cJSON *id = cJSON_GetObjectItemCaseSensitive(signed_event, "id"); cJSON *sig = cJSON_GetObjectItemCaseSensitive(signed_event, "sig"); if (id && cJSON_IsString(id)) { printf(" event id: %s\n", id->valuestring); } if (sig && cJSON_IsString(sig)) { printf(" signature: %s\n", sig->valuestring); } } cJSON_Delete(signed_event); cJSON_Delete(unsigned_event); return NOSTR_SUCCESS; } /* * Demo 3: NIP-44 encrypt and decrypt. * Encrypts a message to ourselves (using our own pubkey as the peer), * then decrypts it to verify round-trip. */ static int demo_nip44(nostr_signer_t *signer, const char *pubkey_hex) { const char *plaintext = "Secret message from n_signer C99 demo!"; char *ciphertext = NULL; char *decrypted = NULL; int rc; printf("\n=== Demo 3: nostr_nip44_encrypt / nostr_nip44_decrypt ===\n"); printf(" plaintext: \"%s\"\n", plaintext); printf(" peer pubkey: %s (self)\n", pubkey_hex); /* Encrypt */ rc = nostr_signer_nip44_encrypt(signer, pubkey_hex, plaintext, &ciphertext); if (rc != NOSTR_SUCCESS) { print_error("nostr_nostr_nip44_encrypt", rc); return rc; } printf(" ciphertext: %s\n", ciphertext); /* Decrypt (using our own pubkey as the sender) */ rc = nostr_signer_nip44_decrypt(signer, pubkey_hex, ciphertext, &decrypted); if (rc != NOSTR_SUCCESS) { print_error("nostr_nostr_nip44_decrypt", rc); free(ciphertext); return rc; } printf(" decrypted: \"%s\"\n", decrypted); /* Verify round-trip */ if (strcmp(plaintext, decrypted) == 0) { printf(" ✓ Round-trip verified: plaintext matches decrypted\n"); } else { printf(" ✗ Round-trip FAILED: plaintext does not match decrypted\n"); rc = NOSTR_ERROR_CRYPTO_FAILED; } free(ciphertext); free(decrypted); return rc; } int main(int argc, char **argv) { const char *target_qube; const char *service_name = "qubes.NsignerRpc"; int nostr_index = 0; nostr_signer_t *signer = NULL; char pubkey_hex[65]; int rc; /* Ignore SIGPIPE — qrexec subprocess may close pipes abruptly */ (void)signal(SIGPIPE, SIG_IGN); if (argc < 2) { fprintf(stderr, "Usage: %s [nostr_index]\n", argv[0]); fprintf(stderr, "Example: %s nostr_signer 1\n", argv[0]); return 1; } target_qube = argv[1]; if (argc > 2) { nostr_index = atoi(argv[2]); } /* Initialize the crypto subsystem */ if (nostr_init() != NOSTR_SUCCESS) { fprintf(stderr, "Failed to initialize crypto subsystem\n"); return 1; } printf("=== n_signer C99 Demo ===\n"); printf("Target qube: %s\n", target_qube); printf("Service: %s\n", service_name); printf("nostr_index: %d\n", nostr_index); printf("\n"); /* Create a high-level signer backed by qrexec transport */ printf("Connecting to n_signer via qrexec...\n"); signer = nostr_signer_nsigner_qrexec(target_qube, service_name, NULL, 30000); if (signer == NULL) { fprintf(stderr, "Failed to create qrexec signer.\n"); fprintf(stderr, "Is qrexec-client-vm available? Is the service installed?\n"); nostr_cleanup(); return 1; } printf("Connected.\n"); /* Select key by nostr_index (NIP-06 m/44'/1237'/N'/0/0) */ rc = nostr_signer_nsigner_set_nostr_index(signer, nostr_index); if (rc != NOSTR_SUCCESS) { print_error("set_nostr_index", rc); nostr_signer_free(signer); nostr_cleanup(); return 1; } /* Demo 1: Get public key */ rc = demo_get_public_key(signer, nostr_index, pubkey_hex, sizeof(pubkey_hex)); if (rc != NOSTR_SUCCESS) { goto cleanup; } /* Demo 2: Sign an event */ rc = demo_sign_event(signer, pubkey_hex); if (rc != NOSTR_SUCCESS) { goto cleanup; } /* Demo 3: NIP-44 encrypt/decrypt */ rc = demo_nip44(signer, pubkey_hex); cleanup: printf("\n=== Summary ===\n"); if (rc == NOSTR_SUCCESS) { printf("All demos completed successfully.\n"); } else { printf("Demo failed with error code %d.\n", rc); } nostr_signer_free(signer); nostr_cleanup(); return (rc == NOSTR_SUCCESS) ? 0 : 1; }