Files
n_signer/client/demo_c99.c

323 lines
9.9 KiB
C

/*
* 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. sign_event — sign a Nostr event (kind 1 text note)
* 3. nip44_encrypt — encrypt a message to a peer (and decrypt it back)
*
* Note: mine_event (NIP-13 PoW) is also available via the JSON-RPC interface.
* See demo_javascript.js and demo_python.py for mine_event usage examples.
* The high-level nostr_signer API does not yet wrap 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 <target_qube> [nostr_index]
* ./build/demo_c99 nostr_signer 1
*
* If no nostr_index is given, defaults to 0.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <signal.h>
#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: 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("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: nip44_encrypt / 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("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("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 <target_qube> [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;
}