#include #include #include #include #include #include #include #include "../src/auth_envelope.h" static int g_failures = 0; static void check_condition(const char *name, int ok) { if (ok) { printf("[PASS] %s\n", name); } else { printf("[FAIL] %s\n", name); g_failures++; } } static int compute_params_hash_hex(cJSON *params, char out_hex[65]) { char *compact; unsigned char hash[32]; if (params == NULL || out_hex == NULL) { return -1; } compact = cJSON_PrintUnformatted(params); if (compact == NULL) { return -1; } if (nostr_sha256((const unsigned char *)compact, strlen(compact), hash) != 0) { free(compact); return -1; } nostr_bytes_to_hex(hash, 32, out_hex); out_hex[64] = '\0'; free(compact); return 0; } static cJSON *tag_pair(const char *k, const char *v) { cJSON *arr = cJSON_CreateArray(); cJSON_AddItemToArray(arr, cJSON_CreateString(k)); cJSON_AddItemToArray(arr, cJSON_CreateString(v)); return arr; } static char *make_request_json(const unsigned char privkey[32], const char *req_id, const char *method, int created_at) { cJSON *params = cJSON_CreateArray(); cJSON *options = cJSON_CreateObject(); cJSON *tags = cJSON_CreateArray(); cJSON *auth; cJSON *root; char body_hash[65]; char *out; cJSON_AddItemToArray(params, cJSON_CreateString("{\"kind\":1,\"content\":\"x\",\"tags\":[],\"created_at\":1700000000}")); cJSON_AddStringToObject(options, "role", "main"); cJSON_AddItemToArray(params, options); if (compute_params_hash_hex(params, body_hash) != 0) { cJSON_Delete(params); return NULL; } cJSON_AddItemToArray(tags, tag_pair("nsigner_rpc", req_id)); cJSON_AddItemToArray(tags, tag_pair("nsigner_method", method)); cJSON_AddItemToArray(tags, tag_pair("nsigner_body_hash", body_hash)); auth = nostr_create_and_sign_event(AUTH_EVENT_KIND, "test-client", tags, privkey, (time_t)created_at); if (auth == NULL) { cJSON_Delete(params); return NULL; } root = cJSON_CreateObject(); cJSON_AddStringToObject(root, "id", req_id); cJSON_AddStringToObject(root, "method", method); cJSON_AddItemToObject(root, "params", params); cJSON_AddItemToObject(root, "auth", auth); out = cJSON_PrintUnformatted(root); cJSON_Delete(root); return out; } int main(void) { auth_nonce_cache_t cache; unsigned char privkey[32] = { 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }; char pubkey_hex[65]; char label[64]; const char *err_msg = NULL; int err_code = 0; char *req; check_condition("nostr_crypto_init", nostr_crypto_init() == 0); auth_nonce_cache_init(&cache); req = make_request_json(privkey, "req-1", "nostr_sign_event", (int)time(NULL)); check_condition("build valid request", req != NULL); if (req != NULL) { check_condition("valid auth envelope accepted", auth_envelope_verify_request(req, &cache, AUTH_DEFAULT_SKEW_SECONDS, pubkey_hex, sizeof(pubkey_hex), label, sizeof(label), &err_code, &err_msg) == 0); check_condition("pubkey output length 64", strlen(pubkey_hex) == 64); free(req); } check_condition("missing auth rejected with required code", auth_envelope_verify_request("{\"id\":\"x\",\"method\":\"get_public_key\",\"params\":[]}", &cache, AUTH_DEFAULT_SKEW_SECONDS, pubkey_hex, sizeof(pubkey_hex), label, sizeof(label), &err_code, &err_msg) != 0 && err_code == AUTH_ERR_ENVELOPE_REQUIRED); req = make_request_json(privkey, "req-2", "get_public_key", (int)time(NULL)); check_condition("build second request", req != NULL); if (req != NULL) { cJSON *root = cJSON_Parse(req); cJSON *method_item; char *tampered; check_condition("parse second request", root != NULL); method_item = (root != NULL) ? cJSON_GetObjectItemCaseSensitive(root, "method") : NULL; if (method_item != NULL) { cJSON_SetValuestring(method_item, "nostr_sign_event"); } tampered = (root != NULL) ? cJSON_PrintUnformatted(root) : NULL; cJSON_Delete(root); check_condition("tamper method mismatch rejected", tampered != NULL && auth_envelope_verify_request(tampered, &cache, AUTH_DEFAULT_SKEW_SECONDS, pubkey_hex, sizeof(pubkey_hex), label, sizeof(label), &err_code, &err_msg) != 0 && err_code == AUTH_ERR_ENVELOPE_MISMATCH); free(tampered); free(req); } req = make_request_json(privkey, "req-3", "nostr_sign_event", (int)time(NULL)); check_condition("build replay request", req != NULL); if (req != NULL) { int first_ok = auth_envelope_verify_request(req, &cache, AUTH_DEFAULT_SKEW_SECONDS, pubkey_hex, sizeof(pubkey_hex), label, sizeof(label), &err_code, &err_msg); int second_ok = auth_envelope_verify_request(req, &cache, AUTH_DEFAULT_SKEW_SECONDS, pubkey_hex, sizeof(pubkey_hex), label, sizeof(label), &err_code, &err_msg); check_condition("first replay request accepted", first_ok == 0); check_condition("second replay request rejected", second_ok != 0 && err_code == AUTH_ERR_REPLAY_DETECTED); free(req); } req = make_request_json(privkey, "req-4", "nostr_sign_event", (int)time(NULL) - 1000); check_condition("build stale request", req != NULL); if (req != NULL) { check_condition("stale request rejected", auth_envelope_verify_request(req, &cache, AUTH_DEFAULT_SKEW_SECONDS, pubkey_hex, sizeof(pubkey_hex), label, sizeof(label), &err_code, &err_msg) != 0 && err_code == AUTH_ERR_ENVELOPE_STALE); free(req); } nostr_crypto_cleanup(); if (g_failures == 0) { printf("ALL TESTS PASSED\n"); return 0; } printf("TESTS FAILED: %d\n", g_failures); return 1; }