v0.0.43 - Add C99 demo program (client/demo_c99.c) demonstrating get_public_key, sign_event, and nip44 encrypt/decrypt via qrexec; sync nostr_core_lib with reconnect fix
This commit is contained in:
7
Makefile
7
Makefile
@@ -44,6 +44,7 @@ EXAMPLE_GET_PUBLIC_KEY_TARGET := $(BUILD_DIR)/example_get_public_key_client
|
||||
EXAMPLE_SIGN_EVENT_TARGET := $(BUILD_DIR)/example_sign_event_client
|
||||
EXAMPLE_GET_PUBKEY_TCP_TARGET := $(BUILD_DIR)/example_get_pubkey_tcp
|
||||
EXAMPLE_GET_PUBKEY_QREXEC_TARGET := $(BUILD_DIR)/example_get_pubkey_qrexec
|
||||
DEMO_C99_TARGET := $(BUILD_DIR)/demo_c99
|
||||
|
||||
.PHONY: all lib dev static static-debug static-arm64 firmware-feather test test-integration test-mnemonic test-mnemonic-input test-role test-selector test-enforcement test-dispatcher test-policy test-socket-name test-auth-envelope test-qrexec-auth examples test-client clean
|
||||
|
||||
@@ -110,7 +111,7 @@ test-qrexec-auth: $(TEST_QREXEC_AUTH_TARGET) $(TARGET_DEV)
|
||||
|
||||
test-client: examples
|
||||
|
||||
examples: $(EXAMPLE_GET_PUBLIC_KEY_TARGET) $(EXAMPLE_SIGN_EVENT_TARGET) $(EXAMPLE_GET_PUBKEY_TCP_TARGET) $(EXAMPLE_GET_PUBKEY_QREXEC_TARGET)
|
||||
examples: $(EXAMPLE_GET_PUBLIC_KEY_TARGET) $(EXAMPLE_SIGN_EVENT_TARGET) $(EXAMPLE_GET_PUBKEY_TCP_TARGET) $(EXAMPLE_GET_PUBKEY_QREXEC_TARGET) $(DEMO_C99_TARGET)
|
||||
|
||||
$(TEST_MNEMONIC_TARGET): $(TEST_DIR)/test_mnemonic.c $(SRC_DIR)/mnemonic.c $(SRC_DIR)/secure_mem.c
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
@@ -172,5 +173,9 @@ $(EXAMPLE_GET_PUBKEY_QREXEC_TARGET): $(EXAMPLES_DIR)/get_pubkey_qrexec.c
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
$(CC) $(CFLAGS) $(EXAMPLES_DIR)/get_pubkey_qrexec.c -o $(EXAMPLE_GET_PUBKEY_QREXEC_TARGET) $(LDFLAGS)
|
||||
|
||||
$(DEMO_C99_TARGET): $(CLIENT_DIR)/demo_c99.c
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
$(CC) $(CFLAGS) $(CLIENT_DIR)/demo_c99.c -o $(DEMO_C99_TARGET) $(LDFLAGS)
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR)
|
||||
|
||||
318
client/demo_c99.c
Normal file
318
client/demo_c99.c
Normal file
@@ -0,0 +1,318 @@
|
||||
/*
|
||||
* 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)
|
||||
*
|
||||
* 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;
|
||||
}
|
||||
@@ -503,8 +503,8 @@ int socket_name_random(char *out, size_t out_len);
|
||||
/* Version information (auto-updated by build/version tooling) */
|
||||
#define NSIGNER_VERSION_MAJOR 0
|
||||
#define NSIGNER_VERSION_MINOR 0
|
||||
#define NSIGNER_VERSION_PATCH 42
|
||||
#define NSIGNER_VERSION "v0.0.42"
|
||||
#define NSIGNER_VERSION_PATCH 43
|
||||
#define NSIGNER_VERSION "v0.0.43"
|
||||
|
||||
|
||||
/* NSIGNER_HEADERLESS_DECLS_END */
|
||||
|
||||
Reference in New Issue
Block a user