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_SIGN_EVENT_TARGET := $(BUILD_DIR)/example_sign_event_client
|
||||||
EXAMPLE_GET_PUBKEY_TCP_TARGET := $(BUILD_DIR)/example_get_pubkey_tcp
|
EXAMPLE_GET_PUBKEY_TCP_TARGET := $(BUILD_DIR)/example_get_pubkey_tcp
|
||||||
EXAMPLE_GET_PUBKEY_QREXEC_TARGET := $(BUILD_DIR)/example_get_pubkey_qrexec
|
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
|
.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
|
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
|
$(TEST_MNEMONIC_TARGET): $(TEST_DIR)/test_mnemonic.c $(SRC_DIR)/mnemonic.c $(SRC_DIR)/secure_mem.c
|
||||||
@mkdir -p $(BUILD_DIR)
|
@mkdir -p $(BUILD_DIR)
|
||||||
@@ -172,5 +173,9 @@ $(EXAMPLE_GET_PUBKEY_QREXEC_TARGET): $(EXAMPLES_DIR)/get_pubkey_qrexec.c
|
|||||||
@mkdir -p $(BUILD_DIR)
|
@mkdir -p $(BUILD_DIR)
|
||||||
$(CC) $(CFLAGS) $(EXAMPLES_DIR)/get_pubkey_qrexec.c -o $(EXAMPLE_GET_PUBKEY_QREXEC_TARGET) $(LDFLAGS)
|
$(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:
|
clean:
|
||||||
rm -rf $(BUILD_DIR)
|
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) */
|
/* Version information (auto-updated by build/version tooling) */
|
||||||
#define NSIGNER_VERSION_MAJOR 0
|
#define NSIGNER_VERSION_MAJOR 0
|
||||||
#define NSIGNER_VERSION_MINOR 0
|
#define NSIGNER_VERSION_MINOR 0
|
||||||
#define NSIGNER_VERSION_PATCH 42
|
#define NSIGNER_VERSION_PATCH 43
|
||||||
#define NSIGNER_VERSION "v0.0.42"
|
#define NSIGNER_VERSION "v0.0.43"
|
||||||
|
|
||||||
|
|
||||||
/* NSIGNER_HEADERLESS_DECLS_END */
|
/* NSIGNER_HEADERLESS_DECLS_END */
|
||||||
|
|||||||
Reference in New Issue
Block a user