Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b80abc3ae | ||
|
|
c81db98e3f | ||
|
|
afe3e4787f | ||
|
|
e3b9248004 | ||
|
|
fcb5abdccf | ||
|
|
92d5927392 | ||
|
|
6c6bcba650 |
@@ -197,7 +197,7 @@ print_info "Auto-detecting needed NIPs from your source code..."
|
||||
NEEDED_NIPS=""
|
||||
if [ -n "$FORCE_NIPS" ]; then
|
||||
if [ "$FORCE_NIPS" = "all" ]; then
|
||||
NEEDED_NIPS="001 004 005 006 011 013 017 019 042 044 046 059 060 061"
|
||||
NEEDED_NIPS="001 004 005 006 011 013 017 019 021 042 044 046 059 060 061"
|
||||
print_info "Forced: Building all available NIPs"
|
||||
else
|
||||
# Convert comma-separated list to space-separated with 3-digit format
|
||||
@@ -216,7 +216,7 @@ else
|
||||
# Check for nostr_core.h (includes everything)
|
||||
if grep -q '#include[[:space:]]*["\<]nostr_core\.h["\>]' *.c 2>/dev/null; then
|
||||
print_info "Found #include \"nostr_core.h\" - building all NIPs"
|
||||
NEEDED_NIPS="001 004 005 006 011 013 019 042 044 046 059 060 061"
|
||||
NEEDED_NIPS="001 004 005 006 011 013 019 021 042 044 046 059 060 061"
|
||||
elif [ -n "$DETECTED" ]; then
|
||||
NEEDED_NIPS="$DETECTED"
|
||||
print_success "Auto-detected NIPs: $(echo $NEEDED_NIPS | tr ' ' ',')"
|
||||
|
||||
+1143
-38
File diff suppressed because it is too large
Load Diff
+63
-1
@@ -17,11 +17,28 @@ extern "C" {
|
||||
#define CASHU_API_VERSION "v1"
|
||||
|
||||
typedef struct {
|
||||
char id[17];
|
||||
char id[NOSTR_CASHU_KEYSET_ID_HEX_SIZE];
|
||||
char unit[16];
|
||||
int active;
|
||||
} cashu_keyset_t;
|
||||
|
||||
typedef enum {
|
||||
CASHU_TOKEN_FORMAT_A = 0,
|
||||
CASHU_TOKEN_FORMAT_B = 1
|
||||
} cashu_token_format_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;
|
||||
@@ -106,12 +123,38 @@ typedef struct {
|
||||
int state_count;
|
||||
} cashu_checkstate_response_t;
|
||||
|
||||
typedef struct {
|
||||
char* mint_url;
|
||||
nostr_cashu_proof_t* proofs;
|
||||
int proof_count;
|
||||
} cashu_decoded_token_t;
|
||||
|
||||
void cashu_mint_set_ca_bundle(const char* ca_bundle_path);
|
||||
|
||||
int cashu_mint_get_info(const char* mint_url,
|
||||
cashu_mint_info_t* info_out,
|
||||
int timeout_seconds);
|
||||
|
||||
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,
|
||||
@@ -203,6 +246,25 @@ int cashu_parse_checkstate_response(cJSON* response_body,
|
||||
|
||||
void cashu_mint_free_checkstate_response(cashu_checkstate_response_t* response);
|
||||
|
||||
int cashu_decode_token(const char* token_string,
|
||||
cashu_decoded_token_t* token_out);
|
||||
|
||||
int cashu_encode_token(const cashu_decoded_token_t* token,
|
||||
cashu_token_format_t format,
|
||||
char** token_string_out);
|
||||
|
||||
void cashu_free_decoded_token(cashu_decoded_token_t* token);
|
||||
|
||||
int cashu_blind_message(const char* secret,
|
||||
const char* mint_pubkey_hex,
|
||||
char out_B_hex[67],
|
||||
unsigned char out_r[32]);
|
||||
|
||||
int cashu_unblind_signature(const char* C_blinded_hex,
|
||||
const char* mint_pubkey_hex,
|
||||
const unsigned char r[32],
|
||||
char out_C_hex[67]);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -237,6 +237,77 @@ int nostr_secp256k1_ecdh(unsigned char *result, const nostr_secp256k1_pubkey *pu
|
||||
return secp256k1_ecdh(g_ctx, result, &internal_pubkey, seckey, hashfp, data);
|
||||
}
|
||||
|
||||
int nostr_secp256k1_ec_pubkey_tweak_mul(nostr_secp256k1_pubkey* pubkey, const unsigned char* tweak32) {
|
||||
if (g_ctx == NULL || pubkey == NULL || tweak32 == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
secp256k1_pubkey internal_pubkey;
|
||||
memcpy(&internal_pubkey, pubkey->data, sizeof(secp256k1_pubkey));
|
||||
|
||||
if (!secp256k1_ec_pubkey_tweak_mul(g_ctx, &internal_pubkey, tweak32)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
memcpy(pubkey->data, &internal_pubkey, sizeof(secp256k1_pubkey));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int nostr_secp256k1_ec_pubkey_negate(nostr_secp256k1_pubkey* pubkey) {
|
||||
if (g_ctx == NULL || pubkey == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
secp256k1_pubkey internal_pubkey;
|
||||
memcpy(&internal_pubkey, pubkey->data, sizeof(secp256k1_pubkey));
|
||||
|
||||
if (!secp256k1_ec_pubkey_negate(g_ctx, &internal_pubkey)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
memcpy(pubkey->data, &internal_pubkey, sizeof(secp256k1_pubkey));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int nostr_secp256k1_ec_pubkey_combine(nostr_secp256k1_pubkey* out,
|
||||
const nostr_secp256k1_pubkey* const* ins,
|
||||
size_t n) {
|
||||
if (g_ctx == NULL || out == NULL || ins == NULL || n == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
secp256k1_pubkey* parsed = (secp256k1_pubkey*)calloc(n, sizeof(secp256k1_pubkey));
|
||||
const secp256k1_pubkey** ptrs = (const secp256k1_pubkey**)calloc(n, sizeof(secp256k1_pubkey*));
|
||||
if (!parsed || !ptrs) {
|
||||
free(parsed);
|
||||
free(ptrs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
if (!ins[i]) {
|
||||
free(parsed);
|
||||
free(ptrs);
|
||||
return 0;
|
||||
}
|
||||
memcpy(&parsed[i], ins[i]->data, sizeof(secp256k1_pubkey));
|
||||
ptrs[i] = &parsed[i];
|
||||
}
|
||||
|
||||
secp256k1_pubkey combined;
|
||||
int ok = secp256k1_ec_pubkey_combine(g_ctx, &combined, ptrs, n);
|
||||
|
||||
free(parsed);
|
||||
free(ptrs);
|
||||
|
||||
if (!ok) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
memcpy(out->data, &combined, sizeof(secp256k1_pubkey));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int nostr_secp256k1_get_random_bytes(unsigned char *buf, size_t len) {
|
||||
if (buf == NULL || len == 0) {
|
||||
return 0;
|
||||
|
||||
+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.3"
|
||||
#define VERSION "v0.5.7"
|
||||
#define VERSION_MAJOR 0
|
||||
#define VERSION_MINOR 5
|
||||
#define VERSION_PATCH 3
|
||||
#define VERSION_PATCH 7
|
||||
|
||||
/*
|
||||
* NOSTR Core Library - Complete API Reference
|
||||
|
||||
@@ -0,0 +1,407 @@
|
||||
/*
|
||||
* NIP-60 Live Cashu Receive Integration Test
|
||||
*
|
||||
* This test performs a live mint against testnut mints, constructs a cashuA token
|
||||
* string from the returned signature, and verifies our receive/decode path.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.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;
|
||||
|
||||
#define TEST_ASSERT(cond, msg) do { \
|
||||
tests_run++; \
|
||||
if (cond) { tests_passed++; printf("✅ %s\n", msg); } \
|
||||
else { printf("❌ %s\n", msg); } \
|
||||
} while (0)
|
||||
|
||||
static int secret_to_Y_hex(const char* secret, char out_Y_hex[67]) {
|
||||
if (!secret || !out_Y_hex) return NOSTR_ERROR_INVALID_INPUT;
|
||||
|
||||
unsigned char digest[32] = {0};
|
||||
if (nostr_sha256((const unsigned char*)secret, strlen(secret), digest) != 0) {
|
||||
return NOSTR_ERROR_CRYPTO_FAILED;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (nostr_sha256(data, sizeof(data), h) != 0) {
|
||||
return NOSTR_ERROR_CRYPTO_FAILED;
|
||||
}
|
||||
|
||||
candidate[0] = 0x02;
|
||||
memcpy(candidate + 1, h, 32);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!in || !out || out_size == 0) return NOSTR_ERROR_INVALID_INPUT;
|
||||
|
||||
size_t needed = ((in_len + 2) / 3) * 4 + 1;
|
||||
char* b64 = (char*)malloc(needed);
|
||||
if (!b64) return NOSTR_ERROR_MEMORY_FAILED;
|
||||
|
||||
size_t n = base64_encode(in, in_len, b64, needed);
|
||||
if (n == 0) {
|
||||
free(b64);
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; i < n && b64[i] != '\0'; i++) {
|
||||
char c = b64[i];
|
||||
if (c == '=') continue;
|
||||
if (c == '+') c = '-';
|
||||
else if (c == '/') c = '_';
|
||||
|
||||
if (j + 1 >= out_size) {
|
||||
free(b64);
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
out[j++] = c;
|
||||
}
|
||||
|
||||
out[j] = '\0';
|
||||
free(b64);
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
static int build_cashuA_token_string(const char* mint_url,
|
||||
const char* keyset_id,
|
||||
uint64_t amount,
|
||||
const char* secret,
|
||||
const char* C_hex,
|
||||
char* out_token,
|
||||
size_t out_token_size) {
|
||||
if (!mint_url || !keyset_id || !secret || !C_hex || !out_token || out_token_size == 0) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
cJSON* token_arr = cJSON_CreateArray();
|
||||
cJSON* entry = cJSON_CreateObject();
|
||||
cJSON* proofs = cJSON_CreateArray();
|
||||
cJSON* proof = cJSON_CreateObject();
|
||||
|
||||
if (!root || !token_arr || !entry || !proofs || !proof) {
|
||||
cJSON_Delete(root);
|
||||
cJSON_Delete(token_arr);
|
||||
cJSON_Delete(entry);
|
||||
cJSON_Delete(proofs);
|
||||
cJSON_Delete(proof);
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
|
||||
cJSON_AddStringToObject(entry, "mint", mint_url);
|
||||
|
||||
cJSON_AddStringToObject(proof, "id", keyset_id);
|
||||
cJSON_AddNumberToObject(proof, "amount", (double)amount);
|
||||
cJSON_AddStringToObject(proof, "secret", secret);
|
||||
cJSON_AddStringToObject(proof, "C", C_hex);
|
||||
|
||||
cJSON_AddItemToArray(proofs, proof);
|
||||
cJSON_AddItemToObject(entry, "proofs", proofs);
|
||||
|
||||
cJSON_AddItemToArray(token_arr, entry);
|
||||
cJSON_AddItemToObject(root, "token", token_arr);
|
||||
|
||||
char* json = cJSON_PrintUnformatted(root);
|
||||
cJSON_Delete(root);
|
||||
if (!json) return NOSTR_ERROR_MEMORY_FAILED;
|
||||
|
||||
char b64url[8192];
|
||||
int rc = base64_to_base64url_no_pad((const unsigned char*)json, strlen(json), b64url, sizeof(b64url));
|
||||
free(json);
|
||||
if (rc != NOSTR_SUCCESS) return rc;
|
||||
|
||||
int written = snprintf(out_token, out_token_size, "cashuA%s", b64url);
|
||||
if (written < 0 || (size_t)written >= out_token_size) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
static void test_live_mint_and_receive(const char* mint_url) {
|
||||
printf("\n=== test_live_mint_and_receive: %s ===\n", mint_url);
|
||||
|
||||
cashu_mint_info_t info;
|
||||
memset(&info, 0, sizeof(info));
|
||||
int rc = cashu_mint_get_info(mint_url, &info, 15);
|
||||
TEST_ASSERT(rc == NOSTR_SUCCESS, "get mint info");
|
||||
if (rc != NOSTR_SUCCESS) return;
|
||||
|
||||
TEST_ASSERT(info.pubkey != NULL && strlen(info.pubkey) == 66, "mint pubkey available");
|
||||
if (!info.pubkey || strlen(info.pubkey) != 66) {
|
||||
cashu_mint_free_info(&info);
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
TEST_ASSERT(rc == NOSTR_SUCCESS, "request mint quote");
|
||||
if (rc != NOSTR_SUCCESS) {
|
||||
cashu_mint_free_info(&info);
|
||||
return;
|
||||
}
|
||||
|
||||
cashu_mint_quote_t quote_check;
|
||||
memset("e_check, 0, sizeof(quote_check));
|
||||
rc = cashu_mint_check_mint_quote(mint_url, quote.quote_id, "e_check, 15);
|
||||
TEST_ASSERT(rc == NOSTR_SUCCESS, "check mint quote");
|
||||
TEST_ASSERT(quote_check.paid == 1, "mint quote paid");
|
||||
if (rc != NOSTR_SUCCESS || quote_check.paid != 1) {
|
||||
cashu_mint_free_info(&info);
|
||||
return;
|
||||
}
|
||||
|
||||
char secret[160];
|
||||
snprintf(secret, sizeof(secret), "nostr-live-receive-test-%ld-%s", (long)time(NULL), mint_url);
|
||||
|
||||
char B_hex[67];
|
||||
unsigned char r[32];
|
||||
rc = cashu_blind_message(secret, info.pubkey, B_hex, r);
|
||||
TEST_ASSERT(rc == NOSTR_SUCCESS, "blind message");
|
||||
if (rc != NOSTR_SUCCESS) {
|
||||
cashu_mint_free_info(&info);
|
||||
return;
|
||||
}
|
||||
|
||||
cashu_blinded_output_t output;
|
||||
memset(&output, 0, sizeof(output));
|
||||
|
||||
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, active_keyset.id, keyset_len + 1);
|
||||
output.amount = 1;
|
||||
snprintf(output.B_, sizeof(output.B_), "%s", B_hex);
|
||||
|
||||
cashu_mint_tokens_request_t mint_req;
|
||||
memset(&mint_req, 0, sizeof(mint_req));
|
||||
snprintf(mint_req.quote_id, sizeof(mint_req.quote_id), "%s", quote.quote_id);
|
||||
snprintf(mint_req.unit, sizeof(mint_req.unit), "sat");
|
||||
mint_req.outputs = &output;
|
||||
mint_req.output_count = 1;
|
||||
|
||||
cJSON* mint_body = NULL;
|
||||
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;
|
||||
}
|
||||
|
||||
cJSON* mint_resp_json = NULL;
|
||||
rc = cashu_mint_mint_tokens(mint_url, mint_body, &mint_resp_json, 15);
|
||||
cJSON_Delete(mint_body);
|
||||
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;
|
||||
}
|
||||
|
||||
cashu_mint_tokens_response_t mint_resp;
|
||||
memset(&mint_resp, 0, sizeof(mint_resp));
|
||||
rc = cashu_parse_mint_tokens_response(mint_resp_json, &mint_resp);
|
||||
cJSON_Delete(mint_resp_json);
|
||||
TEST_ASSERT(rc == NOSTR_SUCCESS, "parse mint tokens response");
|
||||
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_, 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;
|
||||
}
|
||||
|
||||
nostr_cashu_proof_t send_proof;
|
||||
memset(&send_proof, 0, sizeof(send_proof));
|
||||
snprintf(send_proof.id, sizeof(send_proof.id), "%s", active_keyset.id);
|
||||
send_proof.amount = 1;
|
||||
send_proof.secret = secret;
|
||||
send_proof.C = C_hex;
|
||||
|
||||
cashu_decoded_token_t token_for_encode;
|
||||
memset(&token_for_encode, 0, sizeof(token_for_encode));
|
||||
token_for_encode.mint_url = (char*)mint_url;
|
||||
token_for_encode.proofs = &send_proof;
|
||||
token_for_encode.proof_count = 1;
|
||||
|
||||
char* token_a = NULL;
|
||||
rc = cashu_encode_token(&token_for_encode, CASHU_TOKEN_FORMAT_A, &token_a);
|
||||
TEST_ASSERT(rc == NOSTR_SUCCESS && token_a != NULL, "encode cashuA token");
|
||||
if (rc != NOSTR_SUCCESS || !token_a) {
|
||||
cashu_mint_free_mint_tokens_response(&mint_resp);
|
||||
cashu_mint_free_keyset_keys(&keyset_keys);
|
||||
cashu_mint_free_info(&info);
|
||||
return;
|
||||
}
|
||||
|
||||
char* token_b = NULL;
|
||||
rc = cashu_encode_token(&token_for_encode, CASHU_TOKEN_FORMAT_B, &token_b);
|
||||
TEST_ASSERT(rc == NOSTR_SUCCESS && token_b != NULL, "encode cashuB token");
|
||||
if (rc != NOSTR_SUCCESS || !token_b) {
|
||||
free(token_a);
|
||||
cashu_mint_free_mint_tokens_response(&mint_resp);
|
||||
cashu_mint_free_keyset_keys(&keyset_keys);
|
||||
cashu_mint_free_info(&info);
|
||||
return;
|
||||
}
|
||||
|
||||
cashu_decoded_token_t decoded;
|
||||
memset(&decoded, 0, sizeof(decoded));
|
||||
rc = cashu_decode_token(token_a, &decoded);
|
||||
TEST_ASSERT(rc == NOSTR_SUCCESS, "decode cashuA token");
|
||||
TEST_ASSERT(decoded.mint_url != NULL && strcmp(decoded.mint_url, mint_url) == 0, "cashuA decoded mint url matches");
|
||||
TEST_ASSERT(decoded.proof_count == 1, "cashuA decoded proof count");
|
||||
TEST_ASSERT(decoded.proofs != NULL && decoded.proofs[0].amount == 1, "cashuA decoded proof amount");
|
||||
TEST_ASSERT(decoded.proofs != NULL && strcmp(decoded.proofs[0].id, active_keyset.id) == 0, "cashuA decoded keyset id");
|
||||
TEST_ASSERT(decoded.proofs != NULL && decoded.proofs[0].secret != NULL && strcmp(decoded.proofs[0].secret, secret) == 0,
|
||||
"cashuA decoded secret matches");
|
||||
|
||||
cashu_decoded_token_t decoded_b;
|
||||
memset(&decoded_b, 0, sizeof(decoded_b));
|
||||
rc = cashu_decode_token(token_b, &decoded_b);
|
||||
TEST_ASSERT(rc == NOSTR_SUCCESS, "decode cashuB token");
|
||||
TEST_ASSERT(decoded_b.mint_url != NULL && strcmp(decoded_b.mint_url, mint_url) == 0, "cashuB decoded mint url matches");
|
||||
TEST_ASSERT(decoded_b.proof_count == 1, "cashuB decoded proof count");
|
||||
TEST_ASSERT(decoded_b.proofs != NULL && decoded_b.proofs[0].amount == 1, "cashuB decoded proof amount");
|
||||
TEST_ASSERT(decoded_b.proofs != NULL && strcmp(decoded_b.proofs[0].id, active_keyset.id) == 0, "cashuB decoded keyset id");
|
||||
TEST_ASSERT(decoded_b.proofs != NULL && decoded_b.proofs[0].secret != NULL && strcmp(decoded_b.proofs[0].secret, secret) == 0,
|
||||
"cashuB 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_b);
|
||||
cashu_free_decoded_token(&decoded);
|
||||
free(token_b);
|
||||
free(token_a);
|
||||
cashu_mint_free_mint_tokens_response(&mint_resp);
|
||||
cashu_mint_free_keyset_keys(&keyset_keys);
|
||||
cashu_mint_free_info(&info);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("NIP-60 Live Cashu Receive Integration Test\n");
|
||||
printf("========================================\n");
|
||||
|
||||
if (nostr_init() != NOSTR_SUCCESS) {
|
||||
printf("❌ Failed to initialize NOSTR library\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
test_live_mint_and_receive("https://nofee.testnut.cashu.space");
|
||||
|
||||
printf("\n=== Test Summary ===\n");
|
||||
printf("Passed: %d/%d\n", tests_passed, tests_run);
|
||||
|
||||
nostr_cleanup();
|
||||
return (tests_passed == tests_run) ? 0 : 1;
|
||||
}
|
||||
Reference in New Issue
Block a user