Add Cashu keyset endpoints support and per-amount unblinding with live checkstate validation
This commit is contained in:
+198
-32
@@ -226,6 +226,82 @@ static int cashu_call_raw(const char* mint_url,
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
static int cashu_parse_keysets_array(cJSON* keysets,
|
||||
cashu_keyset_t** keysets_out,
|
||||
int* keyset_count_out) {
|
||||
if (!keysets_out || !keyset_count_out) return NOSTR_ERROR_INVALID_INPUT;
|
||||
|
||||
*keysets_out = NULL;
|
||||
*keyset_count_out = 0;
|
||||
|
||||
if (!keysets || !cJSON_IsArray(keysets)) {
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
int n = cJSON_GetArraySize(keysets);
|
||||
if (n <= 0) return NOSTR_SUCCESS;
|
||||
|
||||
cashu_keyset_t* out = (cashu_keyset_t*)calloc((size_t)n, sizeof(cashu_keyset_t));
|
||||
if (!out) return NOSTR_ERROR_MEMORY_FAILED;
|
||||
|
||||
int out_count = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
cJSON* ks = cJSON_GetArrayItem(keysets, i);
|
||||
if (!ks || !cJSON_IsObject(ks)) continue;
|
||||
|
||||
cJSON* id = cJSON_GetObjectItem(ks, "id");
|
||||
cJSON* unit = cJSON_GetObjectItem(ks, "unit");
|
||||
cJSON* active = cJSON_GetObjectItem(ks, "active");
|
||||
|
||||
if (!id || !cJSON_IsString(id) || !id->valuestring || id->valuestring[0] == '\0') continue;
|
||||
|
||||
snprintf(out[out_count].id, sizeof(out[out_count].id), "%s", id->valuestring);
|
||||
if (unit && cJSON_IsString(unit) && unit->valuestring) {
|
||||
snprintf(out[out_count].unit, sizeof(out[out_count].unit), "%s", unit->valuestring);
|
||||
}
|
||||
if (active && cJSON_IsBool(active)) {
|
||||
out[out_count].active = cJSON_IsTrue(active) ? 1 : 0;
|
||||
}
|
||||
|
||||
out_count++;
|
||||
}
|
||||
|
||||
if (out_count == 0) {
|
||||
free(out);
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
*keysets_out = out;
|
||||
*keyset_count_out = out_count;
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
int cashu_mint_get_keysets(const char* mint_url,
|
||||
cashu_keyset_t** keysets_out,
|
||||
int* keyset_count_out,
|
||||
int timeout_seconds) {
|
||||
if (!mint_url || !keysets_out || !keyset_count_out) return NOSTR_ERROR_INVALID_INPUT;
|
||||
|
||||
*keysets_out = NULL;
|
||||
*keyset_count_out = 0;
|
||||
|
||||
cJSON* json = NULL;
|
||||
int rc = cashu_call_raw(mint_url, "/v1/keysets", "GET", NULL, &json, timeout_seconds);
|
||||
if (rc != NOSTR_SUCCESS) {
|
||||
cJSON_Delete(json);
|
||||
return rc;
|
||||
}
|
||||
|
||||
cJSON* keysets = cJSON_GetObjectItem(json, "keysets");
|
||||
rc = cashu_parse_keysets_array(keysets, keysets_out, keyset_count_out);
|
||||
cJSON_Delete(json);
|
||||
return rc;
|
||||
}
|
||||
|
||||
void cashu_mint_free_keysets(cashu_keyset_t* keysets) {
|
||||
free(keysets);
|
||||
}
|
||||
|
||||
int cashu_mint_get_info(const char* mint_url,
|
||||
cashu_mint_info_t* info_out,
|
||||
int timeout_seconds) {
|
||||
@@ -249,41 +325,23 @@ int cashu_mint_get_info(const char* mint_url,
|
||||
if (version && cJSON_IsString(version)) info_out->version = cashu_strdup(cJSON_GetStringValue(version));
|
||||
|
||||
cJSON* keysets = cJSON_GetObjectItem(json, "keysets");
|
||||
if (keysets && cJSON_IsArray(keysets)) {
|
||||
int n = cJSON_GetArraySize(keysets);
|
||||
if (n > 0) {
|
||||
info_out->keysets = (cashu_keyset_t*)calloc((size_t)n, sizeof(cashu_keyset_t));
|
||||
if (!info_out->keysets) {
|
||||
cJSON_Delete(json);
|
||||
cashu_mint_free_info(info_out);
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
rc = cashu_parse_keysets_array(keysets, &info_out->keysets, &info_out->keyset_count);
|
||||
cJSON_Delete(json);
|
||||
if (rc != NOSTR_SUCCESS) {
|
||||
cashu_mint_free_info(info_out);
|
||||
return rc;
|
||||
}
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
cJSON* ks = cJSON_GetArrayItem(keysets, i);
|
||||
if (!ks || !cJSON_IsObject(ks)) continue;
|
||||
|
||||
cJSON* id = cJSON_GetObjectItem(ks, "id");
|
||||
cJSON* unit = cJSON_GetObjectItem(ks, "unit");
|
||||
cJSON* active = cJSON_GetObjectItem(ks, "active");
|
||||
|
||||
if (id && cJSON_IsString(id)) {
|
||||
strncpy(info_out->keysets[info_out->keyset_count].id,
|
||||
cJSON_GetStringValue(id), sizeof(info_out->keysets[info_out->keyset_count].id) - 1);
|
||||
}
|
||||
if (unit && cJSON_IsString(unit)) {
|
||||
strncpy(info_out->keysets[info_out->keyset_count].unit,
|
||||
cJSON_GetStringValue(unit), sizeof(info_out->keysets[info_out->keyset_count].unit) - 1);
|
||||
}
|
||||
if (active && cJSON_IsBool(active)) {
|
||||
info_out->keysets[info_out->keyset_count].active = cJSON_IsTrue(active) ? 1 : 0;
|
||||
}
|
||||
info_out->keyset_count++;
|
||||
}
|
||||
if (!info_out->keysets || info_out->keyset_count == 0) {
|
||||
cashu_keyset_t* fetched = NULL;
|
||||
int fetched_count = 0;
|
||||
rc = cashu_mint_get_keysets(mint_url, &fetched, &fetched_count, timeout_seconds);
|
||||
if (rc == NOSTR_SUCCESS && fetched && fetched_count > 0) {
|
||||
info_out->keysets = fetched;
|
||||
info_out->keyset_count = fetched_count;
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_Delete(json);
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -292,10 +350,118 @@ void cashu_mint_free_info(cashu_mint_info_t* info) {
|
||||
free(info->name);
|
||||
free(info->pubkey);
|
||||
free(info->version);
|
||||
free(info->keysets);
|
||||
cashu_mint_free_keysets(info->keysets);
|
||||
memset(info, 0, sizeof(*info));
|
||||
}
|
||||
|
||||
int cashu_mint_get_keys(const char* mint_url,
|
||||
const char* keyset_id,
|
||||
cashu_keyset_keys_t* keys_out,
|
||||
int timeout_seconds) {
|
||||
if (!mint_url || !keyset_id || !keys_out) return NOSTR_ERROR_INVALID_INPUT;
|
||||
|
||||
memset(keys_out, 0, sizeof(*keys_out));
|
||||
|
||||
char endpoint[640];
|
||||
snprintf(endpoint, sizeof(endpoint), "/v1/keys/%s", keyset_id);
|
||||
|
||||
cJSON* json = NULL;
|
||||
int rc = cashu_call_raw(mint_url, endpoint, "GET", NULL, &json, timeout_seconds);
|
||||
if (rc != NOSTR_SUCCESS) {
|
||||
cJSON_Delete(json);
|
||||
return rc;
|
||||
}
|
||||
|
||||
cJSON* keysets = cJSON_GetObjectItem(json, "keysets");
|
||||
if (!keysets || !cJSON_IsArray(keysets) || cJSON_GetArraySize(keysets) <= 0) {
|
||||
cJSON_Delete(json);
|
||||
return NOSTR_ERROR_CASHU_JSON_PARSE_FAILED;
|
||||
}
|
||||
|
||||
cJSON* ks = cJSON_GetArrayItem(keysets, 0);
|
||||
if (!ks || !cJSON_IsObject(ks)) {
|
||||
cJSON_Delete(json);
|
||||
return NOSTR_ERROR_CASHU_JSON_PARSE_FAILED;
|
||||
}
|
||||
|
||||
cJSON* id = cJSON_GetObjectItem(ks, "id");
|
||||
cJSON* unit = cJSON_GetObjectItem(ks, "unit");
|
||||
cJSON* keys = cJSON_GetObjectItem(ks, "keys");
|
||||
if (!id || !cJSON_IsString(id) || !id->valuestring || !keys || !cJSON_IsObject(keys)) {
|
||||
cJSON_Delete(json);
|
||||
return NOSTR_ERROR_CASHU_JSON_PARSE_FAILED;
|
||||
}
|
||||
|
||||
snprintf(keys_out->keyset_id, sizeof(keys_out->keyset_id), "%s", id->valuestring);
|
||||
if (unit && cJSON_IsString(unit) && unit->valuestring) {
|
||||
snprintf(keys_out->unit, sizeof(keys_out->unit), "%s", unit->valuestring);
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
for (cJSON* it = keys->child; it; it = it->next) {
|
||||
if (!it->string || !cJSON_IsString(it) || !it->valuestring) continue;
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count <= 0) {
|
||||
cJSON_Delete(json);
|
||||
return NOSTR_ERROR_CASHU_JSON_PARSE_FAILED;
|
||||
}
|
||||
|
||||
keys_out->keys = (cashu_amount_key_t*)calloc((size_t)count, sizeof(cashu_amount_key_t));
|
||||
if (!keys_out->keys) {
|
||||
cJSON_Delete(json);
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
|
||||
int out_idx = 0;
|
||||
for (cJSON* it = keys->child; it; it = it->next) {
|
||||
if (!it->string || !cJSON_IsString(it) || !it->valuestring) continue;
|
||||
|
||||
unsigned long long amount = strtoull(it->string, NULL, 10);
|
||||
if (amount == 0) continue;
|
||||
|
||||
keys_out->keys[out_idx].amount = (uint64_t)amount;
|
||||
snprintf(keys_out->keys[out_idx].pubkey, sizeof(keys_out->keys[out_idx].pubkey), "%s", it->valuestring);
|
||||
out_idx++;
|
||||
}
|
||||
|
||||
keys_out->key_count = out_idx;
|
||||
cJSON_Delete(json);
|
||||
|
||||
if (keys_out->key_count <= 0) {
|
||||
cashu_mint_free_keyset_keys(keys_out);
|
||||
return NOSTR_ERROR_CASHU_JSON_PARSE_FAILED;
|
||||
}
|
||||
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
void cashu_mint_free_keyset_keys(cashu_keyset_keys_t* keys) {
|
||||
if (!keys) return;
|
||||
free(keys->keys);
|
||||
memset(keys, 0, sizeof(*keys));
|
||||
}
|
||||
|
||||
int cashu_keyset_keys_find_pubkey_for_amount(const cashu_keyset_keys_t* keys,
|
||||
uint64_t amount,
|
||||
const char** pubkey_out) {
|
||||
if (!keys || !keys->keys || keys->key_count <= 0 || amount == 0 || !pubkey_out) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
*pubkey_out = NULL;
|
||||
|
||||
for (int i = 0; i < keys->key_count; i++) {
|
||||
if (keys->keys[i].amount != amount) continue;
|
||||
if (keys->keys[i].pubkey[0] == '\0') continue;
|
||||
*pubkey_out = keys->keys[i].pubkey;
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
return NOSTR_ERROR_CASHU_INVALID_KEYSET;
|
||||
}
|
||||
|
||||
int cashu_mint_request_mint_quote(const char* mint_url,
|
||||
uint64_t amount,
|
||||
const char* unit,
|
||||
|
||||
@@ -22,6 +22,18 @@ typedef struct {
|
||||
int active;
|
||||
} cashu_keyset_t;
|
||||
|
||||
typedef struct {
|
||||
uint64_t amount;
|
||||
char pubkey[67];
|
||||
} cashu_amount_key_t;
|
||||
|
||||
typedef struct {
|
||||
char keyset_id[65];
|
||||
char unit[16];
|
||||
cashu_amount_key_t* keys;
|
||||
int key_count;
|
||||
} cashu_keyset_keys_t;
|
||||
|
||||
typedef struct {
|
||||
char* name;
|
||||
char* pubkey;
|
||||
@@ -120,6 +132,24 @@ int cashu_mint_get_info(const char* mint_url,
|
||||
|
||||
void cashu_mint_free_info(cashu_mint_info_t* info);
|
||||
|
||||
int cashu_mint_get_keysets(const char* mint_url,
|
||||
cashu_keyset_t** keysets_out,
|
||||
int* keyset_count_out,
|
||||
int timeout_seconds);
|
||||
|
||||
void cashu_mint_free_keysets(cashu_keyset_t* keysets);
|
||||
|
||||
int cashu_mint_get_keys(const char* mint_url,
|
||||
const char* keyset_id,
|
||||
cashu_keyset_keys_t* keys_out,
|
||||
int timeout_seconds);
|
||||
|
||||
void cashu_mint_free_keyset_keys(cashu_keyset_keys_t* keys);
|
||||
|
||||
int cashu_keyset_keys_find_pubkey_for_amount(const cashu_keyset_keys_t* keys,
|
||||
uint64_t amount,
|
||||
const char** pubkey_out);
|
||||
|
||||
int cashu_mint_request_mint_quote(const char* mint_url,
|
||||
uint64_t amount,
|
||||
const char* unit,
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ extern "C" {
|
||||
#define NOSTR_NIP60_HISTORY_KIND 7376
|
||||
#define NOSTR_NIP60_QUOTE_KIND 7374
|
||||
|
||||
#define NOSTR_CASHU_KEYSET_ID_HEX_SIZE 17
|
||||
#define NOSTR_CASHU_KEYSET_ID_HEX_SIZE 65
|
||||
#define NOSTR_CASHU_PUBKEY_HEX_SIZE 67
|
||||
#define NOSTR_CASHU_EVENT_ID_HEX_SIZE 65
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
#define NOSTR_CORE_H
|
||||
|
||||
// Version information (auto-updated by increment_and_push.sh)
|
||||
#define VERSION "v0.5.5"
|
||||
#define VERSION "v0.5.6"
|
||||
#define VERSION_MAJOR 0
|
||||
#define VERSION_MINOR 5
|
||||
#define VERSION_PATCH 5
|
||||
#define VERSION_PATCH 6
|
||||
|
||||
/*
|
||||
* NOSTR Core Library - Complete API Reference
|
||||
|
||||
Binary file not shown.
+100
-118
@@ -9,9 +9,15 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <curl/curl.h>
|
||||
#include "../nostr_core/nostr_core.h"
|
||||
|
||||
/* private secp256k1 wrappers from utils.c */
|
||||
typedef struct {
|
||||
unsigned char data[64];
|
||||
} nostr_secp256k1_pubkey;
|
||||
|
||||
int nostr_secp256k1_ec_pubkey_parse(nostr_secp256k1_pubkey* pubkey, const unsigned char* input, size_t inputlen);
|
||||
|
||||
static int tests_run = 0;
|
||||
static int tests_passed = 0;
|
||||
|
||||
@@ -21,123 +27,45 @@ static int tests_passed = 0;
|
||||
else { printf("❌ %s\n", msg); } \
|
||||
} while (0)
|
||||
|
||||
typedef struct {
|
||||
char* data;
|
||||
size_t size;
|
||||
size_t capacity;
|
||||
} http_buf_t;
|
||||
static int secret_to_Y_hex(const char* secret, char out_Y_hex[67]) {
|
||||
if (!secret || !out_Y_hex) return NOSTR_ERROR_INVALID_INPUT;
|
||||
|
||||
static size_t write_cb(void* contents, size_t size, size_t nmemb, void* userp) {
|
||||
http_buf_t* b = (http_buf_t*)userp;
|
||||
size_t n = size * nmemb;
|
||||
|
||||
if (b->size + n + 1 > b->capacity) {
|
||||
size_t cap = b->capacity ? b->capacity * 2 : 1024;
|
||||
while (cap < b->size + n + 1) cap *= 2;
|
||||
char* p = (char*)realloc(b->data, cap);
|
||||
if (!p) return 0;
|
||||
b->data = p;
|
||||
b->capacity = cap;
|
||||
unsigned char digest[32] = {0};
|
||||
if (nostr_sha256((const unsigned char*)secret, strlen(secret), digest) != 0) {
|
||||
return NOSTR_ERROR_CRYPTO_FAILED;
|
||||
}
|
||||
|
||||
memcpy(b->data + b->size, contents, n);
|
||||
b->size += n;
|
||||
b->data[b->size] = '\0';
|
||||
return n;
|
||||
}
|
||||
unsigned char candidate[33] = {0};
|
||||
for (int counter = 0; counter < 1024; counter++) {
|
||||
unsigned char h[32] = {0};
|
||||
unsigned char data[36] = {0};
|
||||
memcpy(data, digest, 32);
|
||||
data[32] = (unsigned char)((counter >> 24) & 0xff);
|
||||
data[33] = (unsigned char)((counter >> 16) & 0xff);
|
||||
data[34] = (unsigned char)((counter >> 8) & 0xff);
|
||||
data[35] = (unsigned char)(counter & 0xff);
|
||||
|
||||
static int http_get_json(const char* url, cJSON** out_json) {
|
||||
if (!url || !out_json) return NOSTR_ERROR_INVALID_INPUT;
|
||||
*out_json = NULL;
|
||||
if (nostr_sha256(data, sizeof(data), h) != 0) {
|
||||
return NOSTR_ERROR_CRYPTO_FAILED;
|
||||
}
|
||||
|
||||
CURL* curl = curl_easy_init();
|
||||
if (!curl) return NOSTR_ERROR_NETWORK_FAILED;
|
||||
candidate[0] = 0x02;
|
||||
memcpy(candidate + 1, h, 32);
|
||||
|
||||
http_buf_t b;
|
||||
memset(&b, 0, sizeof(b));
|
||||
b.capacity = 2048;
|
||||
b.data = (char*)malloc(b.capacity);
|
||||
if (!b.data) {
|
||||
curl_easy_cleanup(curl);
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
b.data[0] = '\0';
|
||||
nostr_secp256k1_pubkey pub;
|
||||
if (nostr_secp256k1_ec_pubkey_parse(&pub, candidate, sizeof(candidate))) {
|
||||
nostr_bytes_to_hex(candidate, sizeof(candidate), out_Y_hex);
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, (curl_write_callback)write_cb);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &b);
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 15L);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 3L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
|
||||
|
||||
CURLcode rc = curl_easy_perform(curl);
|
||||
long status = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
if (rc != CURLE_OK || status < 200 || status >= 300) {
|
||||
free(b.data);
|
||||
return NOSTR_ERROR_CASHU_HTTP_FAILED;
|
||||
}
|
||||
|
||||
cJSON* json = cJSON_Parse(b.data);
|
||||
free(b.data);
|
||||
if (!json) return NOSTR_ERROR_CASHU_JSON_PARSE_FAILED;
|
||||
|
||||
*out_json = json;
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
static int get_active_keyset_id(const char* mint_url, char out_id[65]) {
|
||||
if (!mint_url || !out_id) return NOSTR_ERROR_INVALID_INPUT;
|
||||
out_id[0] = '\0';
|
||||
|
||||
char url[1024];
|
||||
snprintf(url, sizeof(url), "%s/v1/keysets", mint_url);
|
||||
|
||||
cJSON* root = NULL;
|
||||
int rc = http_get_json(url, &root);
|
||||
if (rc != NOSTR_SUCCESS) return rc;
|
||||
|
||||
cJSON* arr = cJSON_GetObjectItem(root, "keysets");
|
||||
if (!arr || !cJSON_IsArray(arr)) {
|
||||
cJSON_Delete(root);
|
||||
return NOSTR_ERROR_CASHU_JSON_PARSE_FAILED;
|
||||
}
|
||||
|
||||
cJSON* selected = NULL;
|
||||
int n = cJSON_GetArraySize(arr);
|
||||
for (int i = 0; i < n; i++) {
|
||||
cJSON* it = cJSON_GetArrayItem(arr, i);
|
||||
if (!it || !cJSON_IsObject(it)) continue;
|
||||
cJSON* active = cJSON_GetObjectItem(it, "active");
|
||||
if (active && cJSON_IsBool(active) && cJSON_IsTrue(active)) {
|
||||
selected = it;
|
||||
break;
|
||||
candidate[0] = 0x03;
|
||||
if (nostr_secp256k1_ec_pubkey_parse(&pub, candidate, sizeof(candidate))) {
|
||||
nostr_bytes_to_hex(candidate, sizeof(candidate), out_Y_hex);
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
if (!selected && n > 0) {
|
||||
selected = cJSON_GetArrayItem(arr, 0);
|
||||
}
|
||||
|
||||
if (!selected) {
|
||||
cJSON_Delete(root);
|
||||
return NOSTR_ERROR_CASHU_INVALID_KEYSET;
|
||||
}
|
||||
|
||||
cJSON* id = cJSON_GetObjectItem(selected, "id");
|
||||
const char* id_s = (id && cJSON_IsString(id)) ? cJSON_GetStringValue(id) : NULL;
|
||||
if (!id_s || id_s[0] == '\0') {
|
||||
cJSON_Delete(root);
|
||||
return NOSTR_ERROR_CASHU_INVALID_KEYSET;
|
||||
}
|
||||
|
||||
snprintf(out_id, 65, "%s", id_s);
|
||||
cJSON_Delete(root);
|
||||
return NOSTR_SUCCESS;
|
||||
return NOSTR_ERROR_CRYPTO_FAILED;
|
||||
}
|
||||
|
||||
static int base64_to_base64url_no_pad(const unsigned char* in, size_t in_len, char* out, size_t out_size) {
|
||||
@@ -243,15 +171,33 @@ static void test_live_mint_and_receive(const char* mint_url) {
|
||||
return;
|
||||
}
|
||||
|
||||
char keyset_id[65];
|
||||
memset(keyset_id, 0, sizeof(keyset_id));
|
||||
rc = get_active_keyset_id(mint_url, keyset_id);
|
||||
TEST_ASSERT(rc == NOSTR_SUCCESS, "fetch active keyset id");
|
||||
cashu_keyset_t active_keyset;
|
||||
memset(&active_keyset, 0, sizeof(active_keyset));
|
||||
rc = cashu_mint_get_active_keyset(&info, "sat", &active_keyset);
|
||||
TEST_ASSERT(rc == NOSTR_SUCCESS, "select active sat keyset");
|
||||
if (rc != NOSTR_SUCCESS) {
|
||||
cashu_mint_free_info(&info);
|
||||
return;
|
||||
}
|
||||
|
||||
cashu_keyset_keys_t keyset_keys;
|
||||
memset(&keyset_keys, 0, sizeof(keyset_keys));
|
||||
rc = cashu_mint_get_keys(mint_url, active_keyset.id, &keyset_keys, 15);
|
||||
TEST_ASSERT(rc == NOSTR_SUCCESS, "fetch per-amount keyset keys");
|
||||
if (rc != NOSTR_SUCCESS) {
|
||||
cashu_mint_free_info(&info);
|
||||
return;
|
||||
}
|
||||
|
||||
const char* amount_pubkey = NULL;
|
||||
rc = cashu_keyset_keys_find_pubkey_for_amount(&keyset_keys, 1, &amount_pubkey);
|
||||
TEST_ASSERT(rc == NOSTR_SUCCESS && amount_pubkey != NULL, "find pubkey for amount=1");
|
||||
if (rc != NOSTR_SUCCESS || !amount_pubkey) {
|
||||
cashu_mint_free_keyset_keys(&keyset_keys);
|
||||
cashu_mint_free_info(&info);
|
||||
return;
|
||||
}
|
||||
|
||||
cashu_mint_quote_t quote;
|
||||
memset("e, 0, sizeof(quote));
|
||||
rc = cashu_mint_request_mint_quote(mint_url, 1, "sat", "e, 15);
|
||||
@@ -286,14 +232,15 @@ static void test_live_mint_and_receive(const char* mint_url) {
|
||||
cashu_blinded_output_t output;
|
||||
memset(&output, 0, sizeof(output));
|
||||
|
||||
size_t keyset_len = strlen(keyset_id);
|
||||
size_t keyset_len = strlen(active_keyset.id);
|
||||
TEST_ASSERT(keyset_len < sizeof(output.id), "keyset id fits library struct");
|
||||
if (keyset_len >= sizeof(output.id)) {
|
||||
cashu_mint_free_keyset_keys(&keyset_keys);
|
||||
cashu_mint_free_info(&info);
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(output.id, keyset_id, keyset_len + 1);
|
||||
memcpy(output.id, active_keyset.id, keyset_len + 1);
|
||||
output.amount = 1;
|
||||
snprintf(output.B_, sizeof(output.B_), "%s", B_hex);
|
||||
|
||||
@@ -308,6 +255,7 @@ static void test_live_mint_and_receive(const char* mint_url) {
|
||||
rc = cashu_build_mint_tokens_request(&mint_req, &mint_body);
|
||||
TEST_ASSERT(rc == NOSTR_SUCCESS && mint_body != NULL, "build mint tokens request");
|
||||
if (rc != NOSTR_SUCCESS || !mint_body) {
|
||||
cashu_mint_free_keyset_keys(&keyset_keys);
|
||||
cashu_mint_free_info(&info);
|
||||
return;
|
||||
}
|
||||
@@ -318,6 +266,7 @@ static void test_live_mint_and_receive(const char* mint_url) {
|
||||
TEST_ASSERT(rc == NOSTR_SUCCESS && mint_resp_json != NULL, "mint tokens call");
|
||||
if (rc != NOSTR_SUCCESS || !mint_resp_json) {
|
||||
cJSON_Delete(mint_resp_json);
|
||||
cashu_mint_free_keyset_keys(&keyset_keys);
|
||||
cashu_mint_free_info(&info);
|
||||
return;
|
||||
}
|
||||
@@ -330,24 +279,27 @@ static void test_live_mint_and_receive(const char* mint_url) {
|
||||
TEST_ASSERT(mint_resp.signature_count >= 1, "mint returned signatures");
|
||||
if (rc != NOSTR_SUCCESS || mint_resp.signature_count < 1) {
|
||||
cashu_mint_free_mint_tokens_response(&mint_resp);
|
||||
cashu_mint_free_keyset_keys(&keyset_keys);
|
||||
cashu_mint_free_info(&info);
|
||||
return;
|
||||
}
|
||||
|
||||
char C_hex[67];
|
||||
rc = cashu_unblind_signature(mint_resp.signatures[0].C_, info.pubkey, r, C_hex);
|
||||
TEST_ASSERT(rc == NOSTR_SUCCESS, "unblind signature");
|
||||
rc = cashu_unblind_signature(mint_resp.signatures[0].C_, amount_pubkey, r, C_hex);
|
||||
TEST_ASSERT(rc == NOSTR_SUCCESS, "unblind signature with per-amount key");
|
||||
if (rc != NOSTR_SUCCESS) {
|
||||
cashu_mint_free_mint_tokens_response(&mint_resp);
|
||||
cashu_mint_free_keyset_keys(&keyset_keys);
|
||||
cashu_mint_free_info(&info);
|
||||
return;
|
||||
}
|
||||
|
||||
char token_string[10000];
|
||||
rc = build_cashuA_token_string(mint_url, keyset_id, 1, secret, C_hex, token_string, sizeof(token_string));
|
||||
rc = build_cashuA_token_string(mint_url, active_keyset.id, 1, secret, C_hex, token_string, sizeof(token_string));
|
||||
TEST_ASSERT(rc == NOSTR_SUCCESS, "build cashuA token string");
|
||||
if (rc != NOSTR_SUCCESS) {
|
||||
cashu_mint_free_mint_tokens_response(&mint_resp);
|
||||
cashu_mint_free_keyset_keys(&keyset_keys);
|
||||
cashu_mint_free_info(&info);
|
||||
return;
|
||||
}
|
||||
@@ -359,12 +311,42 @@ static void test_live_mint_and_receive(const char* mint_url) {
|
||||
TEST_ASSERT(decoded.mint_url != NULL && strcmp(decoded.mint_url, mint_url) == 0, "decoded mint url matches");
|
||||
TEST_ASSERT(decoded.proof_count == 1, "decoded proof count");
|
||||
TEST_ASSERT(decoded.proofs != NULL && decoded.proofs[0].amount == 1, "decoded proof amount");
|
||||
TEST_ASSERT(decoded.proofs != NULL && strcmp(decoded.proofs[0].id, keyset_id) == 0, "decoded keyset id");
|
||||
TEST_ASSERT(decoded.proofs != NULL && strcmp(decoded.proofs[0].id, active_keyset.id) == 0, "decoded keyset id");
|
||||
TEST_ASSERT(decoded.proofs != NULL && decoded.proofs[0].secret != NULL && strcmp(decoded.proofs[0].secret, secret) == 0,
|
||||
"decoded secret matches");
|
||||
|
||||
char Y_hex[67];
|
||||
rc = secret_to_Y_hex(secret, Y_hex);
|
||||
TEST_ASSERT(rc == NOSTR_SUCCESS, "derive Y from secret for checkstate");
|
||||
if (rc == NOSTR_SUCCESS) {
|
||||
const char* Ys[1] = {Y_hex};
|
||||
cJSON* check_req = NULL;
|
||||
rc = cashu_build_checkstate_request(Ys, 1, &check_req);
|
||||
TEST_ASSERT(rc == NOSTR_SUCCESS && check_req != NULL, "build checkstate request");
|
||||
|
||||
if (rc == NOSTR_SUCCESS && check_req != NULL) {
|
||||
cJSON* check_resp_json = NULL;
|
||||
rc = cashu_mint_check_proofs_state(mint_url, check_req, &check_resp_json, 15);
|
||||
cJSON_Delete(check_req);
|
||||
TEST_ASSERT(rc == NOSTR_SUCCESS && check_resp_json != NULL, "checkstate call");
|
||||
|
||||
if (rc == NOSTR_SUCCESS && check_resp_json != NULL) {
|
||||
cashu_checkstate_response_t state;
|
||||
memset(&state, 0, sizeof(state));
|
||||
rc = cashu_parse_checkstate_response(check_resp_json, &state);
|
||||
cJSON_Delete(check_resp_json);
|
||||
TEST_ASSERT(rc == NOSTR_SUCCESS, "parse checkstate response");
|
||||
TEST_ASSERT(state.state_count >= 1, "checkstate returned at least one state");
|
||||
TEST_ASSERT(state.state_count >= 1 && strcmp(state.states[0].state, "UNSPENT") == 0,
|
||||
"mint reports proof as UNSPENT");
|
||||
cashu_mint_free_checkstate_response(&state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cashu_free_decoded_token(&decoded);
|
||||
cashu_mint_free_mint_tokens_response(&mint_resp);
|
||||
cashu_mint_free_keyset_keys(&keyset_keys);
|
||||
cashu_mint_free_info(&info);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user