Add 10 high-level algorithm-verb wrappers to nostr_signer_t
The high-level nostr_signer_t API only covered 6 of n_signer's 16 verbs (the Nostr protocol verbs + derive_hmac). The other 10 (get_info, get_public_key algorithm-based, sign, verify, encapsulate, decapsulate, derive_shared_secret, otp encrypt/decrypt, mine_event) had no library wrapper — clients had to drop down to the low-level nsigner_client_call and hand-build cJSON params. This adds typed wrappers for all 10: - nostr_signer_get_info - nostr_signer_get_public_key_alg - nostr_signer_sign / nostr_signer_verify - nostr_signer_encapsulate / nostr_signer_decapsulate - nostr_signer_derive_shared_secret - nostr_signer_otp_encrypt / nostr_signer_otp_decrypt - nostr_signer_mine_event The local backend returns NOSTR_ERROR_NOT_SUPPORTED for all 10 (they require signer-side PQ crypto / OTP pads / key derivation the local backend doesn't hold). The remote nsigner backend builds the correct wire params and parses the result. Also adds NOSTR_ERROR_NOT_SUPPORTED (-40) to nostr_common.h. Tests: 2 new tests in nsigner_client_test.c — local-backend NOT_SUPPORTED check for all 10 verbs, and a mock-transport test verifying nostr_signer_sign emits the correct wire request. 8/8 pass.
This commit is contained in:
@@ -39,6 +39,64 @@ int nostr_signer_nip44_decrypt(nostr_signer_t* signer,
|
||||
char** plaintext_out);
|
||||
```
|
||||
|
||||
### 1.1 Algorithm-based verbs (remote nsigner backend only)
|
||||
|
||||
These wrap n_signer's algorithm-based wire verbs. The local backend returns
|
||||
`NOSTR_ERROR_NOT_SUPPORTED` for all of them — they require signer-side key
|
||||
derivation / PQ crypto / OTP pads that the local backend does not hold.
|
||||
|
||||
```c
|
||||
int nostr_signer_get_info(nostr_signer_t* signer, cJSON** info_out);
|
||||
|
||||
int nostr_signer_get_public_key_alg(nostr_signer_t* signer,
|
||||
const char* algorithm, int index,
|
||||
char** result_json_out);
|
||||
|
||||
int nostr_signer_sign(nostr_signer_t* signer,
|
||||
const char* algorithm, int index,
|
||||
const char* scheme, /* "schnorr"|"ecdsa"|NULL */
|
||||
const unsigned char* msg, size_t msg_len,
|
||||
char** result_json_out);
|
||||
|
||||
int nostr_signer_verify(nostr_signer_t* signer,
|
||||
const char* algorithm, int index,
|
||||
const char* scheme,
|
||||
const unsigned char* msg, size_t msg_len,
|
||||
const unsigned char* sig, size_t sig_len,
|
||||
int* valid_out);
|
||||
|
||||
int nostr_signer_encapsulate(nostr_signer_t* signer,
|
||||
const char* peer_pubkey_hex,
|
||||
char** result_json_out);
|
||||
|
||||
int nostr_signer_decapsulate(nostr_signer_t* signer, int index,
|
||||
const char* ciphertext_hex,
|
||||
char** result_json_out);
|
||||
|
||||
int nostr_signer_derive_shared_secret(nostr_signer_t* signer, int index,
|
||||
const char* peer_pubkey_hex,
|
||||
char** result_json_out);
|
||||
|
||||
int nostr_signer_otp_encrypt(nostr_signer_t* signer,
|
||||
const char* plaintext_b64,
|
||||
const char* encoding, /* "ascii"|"binary"|NULL */
|
||||
char** result_json_out);
|
||||
|
||||
int nostr_signer_otp_decrypt(nostr_signer_t* signer,
|
||||
const char* ciphertext,
|
||||
const char* encoding,
|
||||
char** result_json_out);
|
||||
|
||||
int nostr_signer_mine_event(nostr_signer_t* signer,
|
||||
const cJSON* unsigned_event,
|
||||
int difficulty, int timeout_sec, int threads,
|
||||
cJSON** signed_event_out);
|
||||
```
|
||||
|
||||
The `*_result_json_out` functions return a malloc'd copy of the raw JSON
|
||||
result string (caller frees). `nostr_signer_get_info` and
|
||||
`nostr_signer_mine_event` return parsed cJSON objects (caller frees).
|
||||
|
||||
## 2) Remote signer factories + transport API (exact signatures)
|
||||
|
||||
### 2.1 Signer-side remote factories (`nostr_core/nostr_signer.h`)
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#define NOSTR_ERROR_NIP05_NAME_NOT_FOUND -19
|
||||
#define NOSTR_ERROR_NIP05_PUBKEY_MISMATCH -20
|
||||
#define NOSTR_ERROR_EVENT_INVALID_STRUCTURE -30
|
||||
#define NOSTR_ERROR_NOT_SUPPORTED -40 /* verb not supported by this backend */
|
||||
/* nsigner remote backend errors */
|
||||
#define NOSTR_ERROR_NSIGNER_POLICY_DENIED -2001
|
||||
#define NOSTR_ERROR_NSIGNER_INDEX_NOT_ALLOWED -2002
|
||||
|
||||
@@ -592,6 +592,483 @@ static int signer_remote_derive_hmac(nostr_signer_t* signer,
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
/* ---- Remote implementations of the algorithm-based verbs ---- */
|
||||
|
||||
static int signer_remote_get_info(nostr_signer_t* signer, cJSON** info_out) {
|
||||
cJSON* params;
|
||||
cJSON* result = NULL;
|
||||
int rc;
|
||||
|
||||
if (!signer || !info_out) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
*info_out = NULL;
|
||||
|
||||
params = cJSON_CreateArray();
|
||||
if (params == NULL) {
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
|
||||
rc = nsigner_client_call(signer->u.remote.client, "get_info", params, &result);
|
||||
if (rc != NOSTR_SUCCESS) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (cJSON_IsString(result) && result->valuestring != NULL) {
|
||||
cJSON* parsed = cJSON_Parse(result->valuestring);
|
||||
cJSON_Delete(result);
|
||||
if (parsed == NULL || !cJSON_IsObject(parsed)) {
|
||||
cJSON_Delete(parsed);
|
||||
return NOSTR_ERROR_NIP46_INVALID_RESPONSE;
|
||||
}
|
||||
*info_out = parsed;
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
if (cJSON_IsObject(result)) {
|
||||
*info_out = cJSON_Duplicate(result, 1);
|
||||
cJSON_Delete(result);
|
||||
if (*info_out == NULL) {
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
cJSON_Delete(result);
|
||||
return NOSTR_ERROR_NIP46_INVALID_RESPONSE;
|
||||
}
|
||||
|
||||
/* Helper: call a verb whose result is a JSON-object string, return it as a
|
||||
* malloc'd copy of the raw result string (caller frees). Used for verbs
|
||||
* where the caller wants the full JSON object string. */
|
||||
static int signer_remote_call_result_string(nostr_signer_t* signer,
|
||||
const char* method,
|
||||
cJSON* params,
|
||||
char** out) {
|
||||
cJSON* result = NULL;
|
||||
const char* s;
|
||||
size_t n;
|
||||
int rc;
|
||||
|
||||
if (!signer || !method || !out) {
|
||||
if (params) cJSON_Delete(params);
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
*out = NULL;
|
||||
|
||||
rc = nsigner_client_call(signer->u.remote.client, method, params, &result);
|
||||
if (rc != NOSTR_SUCCESS) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (!cJSON_IsString(result) || result->valuestring == NULL) {
|
||||
cJSON_Delete(result);
|
||||
return NOSTR_ERROR_NIP46_INVALID_RESPONSE;
|
||||
}
|
||||
|
||||
s = result->valuestring;
|
||||
n = strlen(s);
|
||||
*out = (char*)malloc(n + 1);
|
||||
if (*out == NULL) {
|
||||
cJSON_Delete(result);
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
memcpy(*out, s, n + 1);
|
||||
cJSON_Delete(result);
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
static int signer_remote_get_public_key_alg(nostr_signer_t* signer,
|
||||
const char* algorithm, int index,
|
||||
char** result_json_out) {
|
||||
cJSON* params;
|
||||
cJSON* opts;
|
||||
|
||||
if (!signer || !algorithm || !result_json_out) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
*result_json_out = NULL;
|
||||
|
||||
params = cJSON_CreateArray();
|
||||
if (params == NULL) {
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
|
||||
opts = cJSON_CreateObject();
|
||||
if (opts == NULL) {
|
||||
cJSON_Delete(params);
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
cJSON_AddStringToObject(opts, "algorithm", algorithm);
|
||||
cJSON_AddNumberToObject(opts, "index", index);
|
||||
cJSON_AddItemToArray(params, opts);
|
||||
|
||||
return signer_remote_call_result_string(signer, "get_public_key", params, result_json_out);
|
||||
}
|
||||
|
||||
static int signer_remote_sign(nostr_signer_t* signer,
|
||||
const char* algorithm, int index,
|
||||
const char* scheme,
|
||||
const unsigned char* msg, size_t msg_len,
|
||||
char** result_json_out) {
|
||||
cJSON* params;
|
||||
cJSON* opts;
|
||||
char* msg_hex;
|
||||
|
||||
if (!signer || !algorithm || !msg || !result_json_out) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
*result_json_out = NULL;
|
||||
|
||||
msg_hex = (char*)malloc(msg_len * 2 + 1);
|
||||
if (msg_hex == NULL) {
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
nostr_bytes_to_hex(msg, msg_len, msg_hex);
|
||||
|
||||
params = cJSON_CreateArray();
|
||||
if (params == NULL) {
|
||||
free(msg_hex);
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
cJSON_AddItemToArray(params, cJSON_CreateString(msg_hex));
|
||||
free(msg_hex);
|
||||
|
||||
opts = cJSON_CreateObject();
|
||||
if (opts == NULL) {
|
||||
cJSON_Delete(params);
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
cJSON_AddStringToObject(opts, "algorithm", algorithm);
|
||||
cJSON_AddNumberToObject(opts, "index", index);
|
||||
if (scheme != NULL && scheme[0] != '\0') {
|
||||
cJSON_AddStringToObject(opts, "scheme", scheme);
|
||||
}
|
||||
cJSON_AddItemToArray(params, opts);
|
||||
|
||||
return signer_remote_call_result_string(signer, "sign", params, result_json_out);
|
||||
}
|
||||
|
||||
static int signer_remote_verify(nostr_signer_t* signer,
|
||||
const char* algorithm, int index,
|
||||
const char* scheme,
|
||||
const unsigned char* msg, size_t msg_len,
|
||||
const unsigned char* sig, size_t sig_len,
|
||||
int* valid_out) {
|
||||
cJSON* params;
|
||||
cJSON* opts;
|
||||
cJSON* result = NULL;
|
||||
cJSON* parsed = NULL;
|
||||
cJSON* valid_item = NULL;
|
||||
char* msg_hex;
|
||||
char* sig_hex;
|
||||
int rc;
|
||||
|
||||
if (!signer || !algorithm || !msg || !sig || !valid_out) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
*valid_out = 0;
|
||||
|
||||
msg_hex = (char*)malloc(msg_len * 2 + 1);
|
||||
if (msg_hex == NULL) {
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
nostr_bytes_to_hex(msg, msg_len, msg_hex);
|
||||
|
||||
sig_hex = (char*)malloc(sig_len * 2 + 1);
|
||||
if (sig_hex == NULL) {
|
||||
free(msg_hex);
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
nostr_bytes_to_hex(sig, sig_len, sig_hex);
|
||||
|
||||
params = cJSON_CreateArray();
|
||||
if (params == NULL) {
|
||||
free(msg_hex);
|
||||
free(sig_hex);
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
cJSON_AddItemToArray(params, cJSON_CreateString(msg_hex));
|
||||
cJSON_AddItemToArray(params, cJSON_CreateString(sig_hex));
|
||||
free(msg_hex);
|
||||
free(sig_hex);
|
||||
|
||||
opts = cJSON_CreateObject();
|
||||
if (opts == NULL) {
|
||||
cJSON_Delete(params);
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
cJSON_AddStringToObject(opts, "algorithm", algorithm);
|
||||
cJSON_AddNumberToObject(opts, "index", index);
|
||||
if (scheme != NULL && scheme[0] != '\0') {
|
||||
cJSON_AddStringToObject(opts, "scheme", scheme);
|
||||
}
|
||||
cJSON_AddItemToArray(params, opts);
|
||||
|
||||
rc = nsigner_client_call(signer->u.remote.client, "verify", params, &result);
|
||||
if (rc != NOSTR_SUCCESS) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* result is a JSON string: {"valid":true,"algorithm":"..."} */
|
||||
if (!cJSON_IsString(result) || result->valuestring == NULL) {
|
||||
cJSON_Delete(result);
|
||||
return NOSTR_ERROR_NIP46_INVALID_RESPONSE;
|
||||
}
|
||||
parsed = cJSON_Parse(result->valuestring);
|
||||
cJSON_Delete(result);
|
||||
if (parsed == NULL) {
|
||||
return NOSTR_ERROR_NIP46_INVALID_RESPONSE;
|
||||
}
|
||||
valid_item = cJSON_GetObjectItemCaseSensitive(parsed, "valid");
|
||||
if (valid_item == NULL || !cJSON_IsBool(valid_item)) {
|
||||
cJSON_Delete(parsed);
|
||||
return NOSTR_ERROR_NIP46_INVALID_RESPONSE;
|
||||
}
|
||||
*valid_out = cJSON_IsTrue(valid_item) ? 1 : 0;
|
||||
cJSON_Delete(parsed);
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
static int signer_remote_encapsulate(nostr_signer_t* signer,
|
||||
const char* peer_pubkey_hex,
|
||||
char** result_json_out) {
|
||||
cJSON* params;
|
||||
cJSON* opts;
|
||||
|
||||
if (!signer || !peer_pubkey_hex || !result_json_out) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
*result_json_out = NULL;
|
||||
|
||||
params = cJSON_CreateArray();
|
||||
if (params == NULL) {
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
cJSON_AddItemToArray(params, cJSON_CreateString(peer_pubkey_hex));
|
||||
|
||||
opts = cJSON_CreateObject();
|
||||
if (opts == NULL) {
|
||||
cJSON_Delete(params);
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
cJSON_AddStringToObject(opts, "algorithm", "ml-kem-768");
|
||||
cJSON_AddItemToArray(params, opts);
|
||||
|
||||
return signer_remote_call_result_string(signer, "encapsulate", params, result_json_out);
|
||||
}
|
||||
|
||||
static int signer_remote_decapsulate(nostr_signer_t* signer, int index,
|
||||
const char* ciphertext_hex,
|
||||
char** result_json_out) {
|
||||
cJSON* params;
|
||||
cJSON* opts;
|
||||
|
||||
if (!signer || !ciphertext_hex || !result_json_out) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
*result_json_out = NULL;
|
||||
|
||||
params = cJSON_CreateArray();
|
||||
if (params == NULL) {
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
cJSON_AddItemToArray(params, cJSON_CreateString(ciphertext_hex));
|
||||
|
||||
opts = cJSON_CreateObject();
|
||||
if (opts == NULL) {
|
||||
cJSON_Delete(params);
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
cJSON_AddStringToObject(opts, "algorithm", "ml-kem-768");
|
||||
cJSON_AddNumberToObject(opts, "index", index);
|
||||
cJSON_AddItemToArray(params, opts);
|
||||
|
||||
return signer_remote_call_result_string(signer, "decapsulate", params, result_json_out);
|
||||
}
|
||||
|
||||
static int signer_remote_derive_shared_secret(nostr_signer_t* signer, int index,
|
||||
const char* peer_pubkey_hex,
|
||||
char** result_json_out) {
|
||||
cJSON* params;
|
||||
cJSON* opts;
|
||||
|
||||
if (!signer || !peer_pubkey_hex || !result_json_out) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
*result_json_out = NULL;
|
||||
|
||||
params = cJSON_CreateArray();
|
||||
if (params == NULL) {
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
cJSON_AddItemToArray(params, cJSON_CreateString(peer_pubkey_hex));
|
||||
|
||||
opts = cJSON_CreateObject();
|
||||
if (opts == NULL) {
|
||||
cJSON_Delete(params);
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
cJSON_AddStringToObject(opts, "algorithm", "x25519");
|
||||
cJSON_AddNumberToObject(opts, "index", index);
|
||||
cJSON_AddItemToArray(params, opts);
|
||||
|
||||
return signer_remote_call_result_string(signer, "derive_shared_secret", params, result_json_out);
|
||||
}
|
||||
|
||||
static int signer_remote_otp_encrypt(nostr_signer_t* signer,
|
||||
const char* plaintext_b64,
|
||||
const char* encoding,
|
||||
char** result_json_out) {
|
||||
cJSON* params;
|
||||
cJSON* opts;
|
||||
|
||||
if (!signer || !plaintext_b64 || !result_json_out) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
*result_json_out = NULL;
|
||||
|
||||
params = cJSON_CreateArray();
|
||||
if (params == NULL) {
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
cJSON_AddItemToArray(params, cJSON_CreateString(plaintext_b64));
|
||||
|
||||
opts = cJSON_CreateObject();
|
||||
if (opts == NULL) {
|
||||
cJSON_Delete(params);
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
cJSON_AddStringToObject(opts, "algorithm", "otp");
|
||||
if (encoding != NULL && encoding[0] != '\0') {
|
||||
cJSON_AddStringToObject(opts, "encoding", encoding);
|
||||
}
|
||||
cJSON_AddItemToArray(params, opts);
|
||||
|
||||
return signer_remote_call_result_string(signer, "encrypt", params, result_json_out);
|
||||
}
|
||||
|
||||
static int signer_remote_otp_decrypt(nostr_signer_t* signer,
|
||||
const char* ciphertext,
|
||||
const char* encoding,
|
||||
char** result_json_out) {
|
||||
cJSON* params;
|
||||
cJSON* opts;
|
||||
|
||||
if (!signer || !ciphertext || !result_json_out) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
*result_json_out = NULL;
|
||||
|
||||
params = cJSON_CreateArray();
|
||||
if (params == NULL) {
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
cJSON_AddItemToArray(params, cJSON_CreateString(ciphertext));
|
||||
|
||||
opts = cJSON_CreateObject();
|
||||
if (opts == NULL) {
|
||||
cJSON_Delete(params);
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
cJSON_AddStringToObject(opts, "algorithm", "otp");
|
||||
if (encoding != NULL && encoding[0] != '\0') {
|
||||
cJSON_AddStringToObject(opts, "encoding", encoding);
|
||||
}
|
||||
cJSON_AddItemToArray(params, opts);
|
||||
|
||||
return signer_remote_call_result_string(signer, "decrypt", params, result_json_out);
|
||||
}
|
||||
|
||||
static int signer_remote_mine_event(nostr_signer_t* signer,
|
||||
const cJSON* unsigned_event,
|
||||
int difficulty, int timeout_sec, int threads,
|
||||
cJSON** signed_event_out) {
|
||||
cJSON* params;
|
||||
cJSON* opts;
|
||||
cJSON* result = NULL;
|
||||
cJSON* parsed = NULL;
|
||||
char* event_json;
|
||||
int rc;
|
||||
|
||||
if (!signer || !unsigned_event || !signed_event_out) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
*signed_event_out = NULL;
|
||||
|
||||
event_json = cJSON_PrintUnformatted((cJSON*)unsigned_event);
|
||||
if (event_json == NULL) {
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
|
||||
params = cJSON_CreateArray();
|
||||
if (params == NULL) {
|
||||
free(event_json);
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
cJSON_AddItemToArray(params, cJSON_CreateString(event_json));
|
||||
free(event_json);
|
||||
|
||||
opts = cJSON_CreateObject();
|
||||
if (opts == NULL) {
|
||||
cJSON_Delete(params);
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
if (signer->u.remote.role[0] != '\0') {
|
||||
cJSON_AddStringToObject(opts, "role", signer->u.remote.role);
|
||||
}
|
||||
if (signer->u.remote.has_role_path && signer->u.remote.role_path[0] != '\0') {
|
||||
cJSON_AddStringToObject(opts, "role_path", signer->u.remote.role_path);
|
||||
}
|
||||
if (difficulty > 0) {
|
||||
cJSON_AddNumberToObject(opts, "difficulty", difficulty);
|
||||
}
|
||||
if (timeout_sec > 0) {
|
||||
cJSON_AddNumberToObject(opts, "timeout_sec", timeout_sec);
|
||||
}
|
||||
if (threads > 0) {
|
||||
cJSON_AddNumberToObject(opts, "threads", threads);
|
||||
}
|
||||
cJSON_AddItemToArray(params, opts);
|
||||
|
||||
rc = nsigner_client_call(signer->u.remote.client, "nostr_mine_event", params, &result);
|
||||
if (rc != NOSTR_SUCCESS) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (cJSON_IsString(result) && result->valuestring != NULL) {
|
||||
parsed = cJSON_Parse(result->valuestring);
|
||||
cJSON_Delete(result);
|
||||
if (parsed == NULL || !cJSON_IsObject(parsed)) {
|
||||
cJSON_Delete(parsed);
|
||||
return NOSTR_ERROR_NIP46_INVALID_RESPONSE;
|
||||
}
|
||||
*signed_event_out = parsed;
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
if (cJSON_IsObject(result)) {
|
||||
*signed_event_out = cJSON_Duplicate(result, 1);
|
||||
cJSON_Delete(result);
|
||||
if (*signed_event_out == NULL) {
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
cJSON_Delete(result);
|
||||
return NOSTR_ERROR_NIP46_INVALID_RESPONSE;
|
||||
}
|
||||
|
||||
#endif /* NOSTR_ENABLE_NSIGNER_CLIENT */
|
||||
|
||||
nostr_signer_t* nostr_signer_local(const unsigned char private_key[32]) {
|
||||
@@ -766,6 +1243,204 @@ int nostr_signer_derive_hmac(nostr_signer_t* signer,
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
/* ---- Public dispatch for the algorithm-based verbs ---- */
|
||||
|
||||
int nostr_signer_get_info(nostr_signer_t* signer, cJSON** info_out) {
|
||||
if (!signer || !info_out) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
if (signer->backend == NOSTR_SIGNER_BACKEND_LOCAL) {
|
||||
return NOSTR_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
#if defined(NOSTR_ENABLE_NSIGNER_CLIENT)
|
||||
if (signer->backend == NOSTR_SIGNER_BACKEND_NSIGNER_REMOTE) {
|
||||
return signer_remote_get_info(signer, info_out);
|
||||
}
|
||||
#endif
|
||||
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
int nostr_signer_get_public_key_alg(nostr_signer_t* signer,
|
||||
const char* algorithm, int index,
|
||||
char** result_json_out) {
|
||||
if (!signer || !algorithm || !result_json_out) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
if (signer->backend == NOSTR_SIGNER_BACKEND_LOCAL) {
|
||||
return NOSTR_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
#if defined(NOSTR_ENABLE_NSIGNER_CLIENT)
|
||||
if (signer->backend == NOSTR_SIGNER_BACKEND_NSIGNER_REMOTE) {
|
||||
return signer_remote_get_public_key_alg(signer, algorithm, index, result_json_out);
|
||||
}
|
||||
#endif
|
||||
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
int nostr_signer_sign(nostr_signer_t* signer,
|
||||
const char* algorithm, int index,
|
||||
const char* scheme,
|
||||
const unsigned char* msg, size_t msg_len,
|
||||
char** result_json_out) {
|
||||
if (!signer || !algorithm || !msg || !result_json_out) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
if (signer->backend == NOSTR_SIGNER_BACKEND_LOCAL) {
|
||||
return NOSTR_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
#if defined(NOSTR_ENABLE_NSIGNER_CLIENT)
|
||||
if (signer->backend == NOSTR_SIGNER_BACKEND_NSIGNER_REMOTE) {
|
||||
return signer_remote_sign(signer, algorithm, index, scheme, msg, msg_len, result_json_out);
|
||||
}
|
||||
#endif
|
||||
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
int nostr_signer_verify(nostr_signer_t* signer,
|
||||
const char* algorithm, int index,
|
||||
const char* scheme,
|
||||
const unsigned char* msg, size_t msg_len,
|
||||
const unsigned char* sig, size_t sig_len,
|
||||
int* valid_out) {
|
||||
if (!signer || !algorithm || !msg || !sig || !valid_out) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
if (signer->backend == NOSTR_SIGNER_BACKEND_LOCAL) {
|
||||
return NOSTR_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
#if defined(NOSTR_ENABLE_NSIGNER_CLIENT)
|
||||
if (signer->backend == NOSTR_SIGNER_BACKEND_NSIGNER_REMOTE) {
|
||||
return signer_remote_verify(signer, algorithm, index, scheme, msg, msg_len, sig, sig_len, valid_out);
|
||||
}
|
||||
#endif
|
||||
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
int nostr_signer_encapsulate(nostr_signer_t* signer,
|
||||
const char* peer_pubkey_hex,
|
||||
char** result_json_out) {
|
||||
if (!signer || !peer_pubkey_hex || !result_json_out) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
if (signer->backend == NOSTR_SIGNER_BACKEND_LOCAL) {
|
||||
return NOSTR_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
#if defined(NOSTR_ENABLE_NSIGNER_CLIENT)
|
||||
if (signer->backend == NOSTR_SIGNER_BACKEND_NSIGNER_REMOTE) {
|
||||
return signer_remote_encapsulate(signer, peer_pubkey_hex, result_json_out);
|
||||
}
|
||||
#endif
|
||||
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
int nostr_signer_decapsulate(nostr_signer_t* signer, int index,
|
||||
const char* ciphertext_hex,
|
||||
char** result_json_out) {
|
||||
if (!signer || !ciphertext_hex || !result_json_out) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
if (signer->backend == NOSTR_SIGNER_BACKEND_LOCAL) {
|
||||
return NOSTR_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
#if defined(NOSTR_ENABLE_NSIGNER_CLIENT)
|
||||
if (signer->backend == NOSTR_SIGNER_BACKEND_NSIGNER_REMOTE) {
|
||||
return signer_remote_decapsulate(signer, index, ciphertext_hex, result_json_out);
|
||||
}
|
||||
#endif
|
||||
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
int nostr_signer_derive_shared_secret(nostr_signer_t* signer, int index,
|
||||
const char* peer_pubkey_hex,
|
||||
char** result_json_out) {
|
||||
if (!signer || !peer_pubkey_hex || !result_json_out) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
if (signer->backend == NOSTR_SIGNER_BACKEND_LOCAL) {
|
||||
return NOSTR_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
#if defined(NOSTR_ENABLE_NSIGNER_CLIENT)
|
||||
if (signer->backend == NOSTR_SIGNER_BACKEND_NSIGNER_REMOTE) {
|
||||
return signer_remote_derive_shared_secret(signer, index, peer_pubkey_hex, result_json_out);
|
||||
}
|
||||
#endif
|
||||
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
int nostr_signer_otp_encrypt(nostr_signer_t* signer,
|
||||
const char* plaintext_b64,
|
||||
const char* encoding,
|
||||
char** result_json_out) {
|
||||
if (!signer || !plaintext_b64 || !result_json_out) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
if (signer->backend == NOSTR_SIGNER_BACKEND_LOCAL) {
|
||||
return NOSTR_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
#if defined(NOSTR_ENABLE_NSIGNER_CLIENT)
|
||||
if (signer->backend == NOSTR_SIGNER_BACKEND_NSIGNER_REMOTE) {
|
||||
return signer_remote_otp_encrypt(signer, plaintext_b64, encoding, result_json_out);
|
||||
}
|
||||
#endif
|
||||
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
int nostr_signer_otp_decrypt(nostr_signer_t* signer,
|
||||
const char* ciphertext,
|
||||
const char* encoding,
|
||||
char** result_json_out) {
|
||||
if (!signer || !ciphertext || !result_json_out) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
if (signer->backend == NOSTR_SIGNER_BACKEND_LOCAL) {
|
||||
return NOSTR_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
#if defined(NOSTR_ENABLE_NSIGNER_CLIENT)
|
||||
if (signer->backend == NOSTR_SIGNER_BACKEND_NSIGNER_REMOTE) {
|
||||
return signer_remote_otp_decrypt(signer, ciphertext, encoding, result_json_out);
|
||||
}
|
||||
#endif
|
||||
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
int nostr_signer_mine_event(nostr_signer_t* signer,
|
||||
const cJSON* unsigned_event,
|
||||
int difficulty, int timeout_sec, int threads,
|
||||
cJSON** signed_event_out) {
|
||||
if (!signer || !unsigned_event || !signed_event_out) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
if (signer->backend == NOSTR_SIGNER_BACKEND_LOCAL) {
|
||||
return NOSTR_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
#if defined(NOSTR_ENABLE_NSIGNER_CLIENT)
|
||||
if (signer->backend == NOSTR_SIGNER_BACKEND_NSIGNER_REMOTE) {
|
||||
return signer_remote_mine_event(signer, unsigned_event, difficulty, timeout_sec, threads, signed_event_out);
|
||||
}
|
||||
#endif
|
||||
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
#if defined(NOSTR_ENABLE_NSIGNER_CLIENT)
|
||||
static nostr_signer_t* nostr_signer_nsigner_from_transport(nsigner_transport_t* transport, const char* role) {
|
||||
nsigner_client_t* client = NULL;
|
||||
|
||||
@@ -47,6 +47,95 @@ int nostr_signer_nip44_decrypt(nostr_signer_t* signer,
|
||||
const char* ciphertext,
|
||||
char** plaintext_out);
|
||||
|
||||
/* ---- Algorithm-based verbs (remote nsigner backend only) ----
|
||||
*
|
||||
* These wrap n_signer's algorithm-based wire verbs. The local backend
|
||||
* returns NOSTR_ERROR_NOT_SUPPORTED for all of them — they require
|
||||
* signer-side key derivation / PQ crypto / OTP pads that the local
|
||||
* backend does not hold. Use a remote nsigner signer for these.
|
||||
*
|
||||
* `algorithm` is one of: "secp256k1", "ed25519", "x25519", "ml-dsa-65",
|
||||
* "slh-dsa-128s", "ml-kem-768", "otp" (see n_signer README §4.4).
|
||||
* `index` is the algorithm derivation index substituted into the alg's
|
||||
* derivation path. `scheme` is secp256k1-only: "schnorr" (default) or
|
||||
* "ecdsa"; pass NULL for the default.
|
||||
*/
|
||||
|
||||
/* Signer metadata. Returns a cJSON object (caller frees) with name,
|
||||
* implementation, version, verbs, algorithms, etc. */
|
||||
int nostr_signer_get_info(nostr_signer_t* signer, cJSON** info_out);
|
||||
|
||||
/* Algorithm-based public key. Returns a malloc'd string (caller frees):
|
||||
* for most algorithms this is a JSON object string
|
||||
* {"algorithm":"...","public_key":"<hex>","key_id":"<16hex>"}; for
|
||||
* nostr_get_public_key-style plain output see nostr_signer_get_public_key. */
|
||||
int nostr_signer_get_public_key_alg(nostr_signer_t* signer,
|
||||
const char* algorithm, int index,
|
||||
char** result_json_out);
|
||||
|
||||
/* Sign a message. msg/msg_len are raw bytes; the wire sends hex.
|
||||
* Returns a malloc'd JSON string (caller frees):
|
||||
* {"signature":"<hex>","algorithm":"...","key_id":"<16hex>"}. */
|
||||
int nostr_signer_sign(nostr_signer_t* signer,
|
||||
const char* algorithm, int index,
|
||||
const char* scheme,
|
||||
const unsigned char* msg, size_t msg_len,
|
||||
char** result_json_out);
|
||||
|
||||
/* Verify a signature. sig/sig_len are raw bytes. Sets *valid_out to 1
|
||||
* or 0. Returns NOSTR_SUCCESS or an error code. */
|
||||
int nostr_signer_verify(nostr_signer_t* signer,
|
||||
const char* algorithm, int index,
|
||||
const char* scheme,
|
||||
const unsigned char* msg, size_t msg_len,
|
||||
const unsigned char* sig, size_t sig_len,
|
||||
int* valid_out);
|
||||
|
||||
/* ML-KEM-768 encapsulate. peer_pubkey_hex is the recipient's 1184-byte
|
||||
* (2368 hex) ML-KEM public key. Returns malloc'd JSON strings (caller
|
||||
* frees): {"ciphertext":"<hex>","shared_secret":"<hex>","algorithm":"ml-kem-768"}. */
|
||||
int nostr_signer_encapsulate(nostr_signer_t* signer,
|
||||
const char* peer_pubkey_hex,
|
||||
char** result_json_out);
|
||||
|
||||
/* ML-KEM-768 decapsulate. ciphertext_hex is the encapsulated ciphertext.
|
||||
* Returns malloc'd JSON string (caller frees):
|
||||
* {"shared_secret":"<hex>","algorithm":"ml-kem-768"}. */
|
||||
int nostr_signer_decapsulate(nostr_signer_t* signer, int index,
|
||||
const char* ciphertext_hex,
|
||||
char** result_json_out);
|
||||
|
||||
/* X25519 derive_shared_secret. peer_pubkey_hex is the peer's 32-byte
|
||||
* (64 hex) X25519 public key. Returns malloc'd JSON string (caller frees):
|
||||
* {"shared_secret":"<hex>","algorithm":"x25519"}. */
|
||||
int nostr_signer_derive_shared_secret(nostr_signer_t* signer, int index,
|
||||
const char* peer_pubkey_hex,
|
||||
char** result_json_out);
|
||||
|
||||
/* OTP encrypt. plaintext_b64 is base64-encoded plaintext. encoding is
|
||||
* "ascii" (default) or "binary"; pass NULL for default. Returns malloc'd
|
||||
* JSON string (caller frees) with ciphertext, encoding, pad offsets. */
|
||||
int nostr_signer_otp_encrypt(nostr_signer_t* signer,
|
||||
const char* plaintext_b64,
|
||||
const char* encoding,
|
||||
char** result_json_out);
|
||||
|
||||
/* OTP decrypt. ciphertext is the ASCII-armored or base64 blob. encoding
|
||||
* is "ascii"/"binary" or NULL for auto-detect. Returns malloc'd JSON
|
||||
* string (caller frees) with plaintext + pad offsets. */
|
||||
int nostr_signer_otp_decrypt(nostr_signer_t* signer,
|
||||
const char* ciphertext,
|
||||
const char* encoding,
|
||||
char** result_json_out);
|
||||
|
||||
/* Nostr mine-event (proof-of-work). difficulty is target leading zero
|
||||
* bits; timeout_sec and threads are optional (pass 0/1 for defaults).
|
||||
* Returns the signed+mined event as a cJSON object (caller frees). */
|
||||
int nostr_signer_mine_event(nostr_signer_t* signer,
|
||||
const cJSON* unsigned_event,
|
||||
int difficulty, int timeout_sec, int threads,
|
||||
cJSON** signed_event_out);
|
||||
|
||||
#if defined(NOSTR_ENABLE_NSIGNER_CLIENT)
|
||||
nostr_signer_t* nostr_signer_nsigner_unix(const char* socket_name, const char* role, int timeout_ms);
|
||||
nostr_signer_t* nostr_signer_nsigner_serial(const char* device_path, const char* role, int timeout_ms);
|
||||
|
||||
@@ -408,6 +408,143 @@ static int test_local_derive_hmac_matches_reference(void) {
|
||||
return ok;
|
||||
}
|
||||
|
||||
/* The algorithm-based verbs are remote-only. The local backend must return
|
||||
* NOSTR_ERROR_NOT_SUPPORTED for each of them. */
|
||||
static int test_local_backend_not_supported(void) {
|
||||
unsigned char privkey[32];
|
||||
nostr_signer_t* signer = NULL;
|
||||
int ok = 1;
|
||||
int i;
|
||||
char* out = NULL;
|
||||
cJSON* info = NULL;
|
||||
int valid = 0;
|
||||
unsigned char msg[4] = { 0x68, 0x65, 0x6c, 0x6f }; /* "helo" */
|
||||
|
||||
for (i = 0; i < 32; i++) {
|
||||
privkey[i] = (unsigned char)(i + 1);
|
||||
}
|
||||
|
||||
signer = nostr_signer_local(privkey);
|
||||
if (signer == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ok = ok && (nostr_signer_get_info(signer, &info) == NOSTR_ERROR_NOT_SUPPORTED);
|
||||
ok = ok && (nostr_signer_get_public_key_alg(signer, "secp256k1", 0, &out) == NOSTR_ERROR_NOT_SUPPORTED);
|
||||
ok = ok && (nostr_signer_sign(signer, "ed25519", 0, NULL, msg, 4, &out) == NOSTR_ERROR_NOT_SUPPORTED);
|
||||
ok = ok && (nostr_signer_verify(signer, "ed25519", 0, NULL, msg, 4, msg, 4, &valid) == NOSTR_ERROR_NOT_SUPPORTED);
|
||||
ok = ok && (nostr_signer_encapsulate(signer, "00", &out) == NOSTR_ERROR_NOT_SUPPORTED);
|
||||
ok = ok && (nostr_signer_decapsulate(signer, 0, "00", &out) == NOSTR_ERROR_NOT_SUPPORTED);
|
||||
ok = ok && (nostr_signer_derive_shared_secret(signer, 0, "00", &out) == NOSTR_ERROR_NOT_SUPPORTED);
|
||||
ok = ok && (nostr_signer_otp_encrypt(signer, "aGVsbG8=", NULL, &out) == NOSTR_ERROR_NOT_SUPPORTED);
|
||||
ok = ok && (nostr_signer_otp_decrypt(signer, "blob", NULL, &out) == NOSTR_ERROR_NOT_SUPPORTED);
|
||||
{
|
||||
cJSON* evt = cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(evt, "kind", 1);
|
||||
ok = ok && (nostr_signer_mine_event(signer, evt, 4, 0, 1, &info) == NOSTR_ERROR_NOT_SUPPORTED);
|
||||
cJSON_Delete(evt);
|
||||
}
|
||||
|
||||
nostr_signer_free(signer);
|
||||
return ok;
|
||||
}
|
||||
|
||||
/* Remote wrappers build correct wire requests. Uses a mock fds transport:
|
||||
* the child asserts the method + key fields and returns a canned response.
|
||||
* nostr_signer_nsigner_fds takes read_fd/write_fd and builds its own
|
||||
* transport internally, so we pass the socketpair fds directly. */
|
||||
static int test_remote_wrappers_build_correct_wire(void) {
|
||||
int fds[2] = { -1, -1 };
|
||||
pid_t pid = -1;
|
||||
int status = 0;
|
||||
int ok = 0;
|
||||
nostr_signer_t* signer = NULL;
|
||||
char* result_str = NULL;
|
||||
|
||||
if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
unsigned char hdr[4];
|
||||
unsigned int req_len = 0;
|
||||
char* req = NULL;
|
||||
const char* response = "{\"id\":\"1\",\"result\":\"{\\\"signature\\\":\\\"aabb\\\",\\\"algorithm\\\":\\\"ed25519\\\",\\\"key_id\\\":\\\"ccdd\\\"}\"}";
|
||||
unsigned int res_len = (unsigned int)strlen(response);
|
||||
unsigned char out_hdr[4];
|
||||
|
||||
close(fds[0]);
|
||||
|
||||
if (read_exact_fd(fds[1], hdr, sizeof(hdr)) != 0) { _exit(2); }
|
||||
req_len = ((unsigned int)hdr[0] << 24) | ((unsigned int)hdr[1] << 16) |
|
||||
((unsigned int)hdr[2] << 8) | (unsigned int)hdr[3];
|
||||
if (req_len == 0 || req_len > 65536U) { _exit(3); }
|
||||
req = (char*)malloc((size_t)req_len + 1U);
|
||||
if (req == NULL) { _exit(4); }
|
||||
if (read_exact_fd(fds[1], (unsigned char*)req, req_len) != 0) { free(req); _exit(5); }
|
||||
req[req_len] = '\0';
|
||||
|
||||
/* nostr_signer_sign(ed25519, 0, NULL, "helo") must emit:
|
||||
* method "sign", positional msg hex "68656c6f",
|
||||
* options algorithm=ed25519, index=0, no scheme field. */
|
||||
if (strstr(req, "\"method\":\"sign\"") == NULL) { free(req); _exit(6); }
|
||||
if (strstr(req, "\"68656c6f\"") == NULL) { free(req); _exit(7); }
|
||||
if (strstr(req, "\"algorithm\":\"ed25519\"") == NULL) { free(req); _exit(8); }
|
||||
if (strstr(req, "\"index\":0") == NULL) { free(req); _exit(9); }
|
||||
if (strstr(req, "\"scheme\"") != NULL) { free(req); _exit(10); } /* scheme omitted when NULL */
|
||||
free(req);
|
||||
|
||||
out_hdr[0] = (unsigned char)((res_len >> 24) & 0xFFU);
|
||||
out_hdr[1] = (unsigned char)((res_len >> 16) & 0xFFU);
|
||||
out_hdr[2] = (unsigned char)((res_len >> 8) & 0xFFU);
|
||||
out_hdr[3] = (unsigned char)(res_len & 0xFFU);
|
||||
if (write_full_fd(fds[1], out_hdr, sizeof(out_hdr)) != 0) { _exit(11); }
|
||||
if (write_full_fd(fds[1], (const unsigned char*)response, res_len) != 0) { _exit(12); }
|
||||
close(fds[1]);
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
close(fds[1]);
|
||||
fds[1] = -1;
|
||||
|
||||
/* The high-level signer takes ownership of the fds via its internal
|
||||
* transport. Pass the client side of the socketpair. */
|
||||
signer = nostr_signer_nsigner_fds(fds[0], fds[0], NULL, 3000);
|
||||
if (signer == NULL) {
|
||||
goto cleanup;
|
||||
}
|
||||
fds[0] = -1; /* owned by signer now */
|
||||
|
||||
{
|
||||
unsigned char msg[4] = { 0x68, 0x65, 0x6c, 0x6f };
|
||||
if (nostr_signer_sign(signer, "ed25519", 0, NULL, msg, 4, &result_str) != NOSTR_SUCCESS) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (result_str == NULL || strstr(result_str, "aabb") == NULL) {
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
if (waitpid(pid, &status, 0) < 0) { goto cleanup; }
|
||||
pid = -1;
|
||||
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { goto cleanup; }
|
||||
|
||||
ok = 1;
|
||||
|
||||
cleanup:
|
||||
if (result_str) free(result_str);
|
||||
if (signer) nostr_signer_free(signer);
|
||||
if (fds[0] >= 0) close(fds[0]);
|
||||
if (fds[1] >= 0) close(fds[1]);
|
||||
if (pid > 0) { (void)waitpid(pid, &status, 0); }
|
||||
return ok;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
#if !defined(NOSTR_ENABLE_NSIGNER_CLIENT)
|
||||
printf("nsigner client disabled in this build; skipping\n");
|
||||
@@ -419,6 +556,8 @@ int main(void) {
|
||||
run_result("/proc/net/unix discovery parser no-crash", test_unix_discovery_no_crash());
|
||||
run_result("fds transport client round-trip over framed fd pair", test_fds_transport_round_trip_via_client());
|
||||
run_result("local signer derive_hmac matches reference HMAC-SHA256", test_local_derive_hmac_matches_reference());
|
||||
run_result("local backend returns NOT_SUPPORTED for algorithm verbs", test_local_backend_not_supported());
|
||||
run_result("remote wrappers build correct wire (sign ed25519)", test_remote_wrappers_build_correct_wire());
|
||||
|
||||
printf("\nTotal: %d Passed: %d Failed: %d\n", test_count, passed_count, test_count - passed_count);
|
||||
return (test_count == passed_count) ? 0 : 1;
|
||||
|
||||
Reference in New Issue
Block a user